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:
shamoon 2022-03-11 12:31:21 -08:00 committed by GitHub
commit ef2b4a7536
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
165 changed files with 3979 additions and 2808 deletions

View File

@ -1,10 +1,9 @@
--- ---
name: Bug report name: Bug report
about: Something is not working about: Something is not working
title: "[BUG] Concise description of the issue" title: '[BUG] Concise description of the issue'
labels: '' labels: ''
assignees: '' assignees: ''
--- ---
<!--- <!---
@ -24,6 +23,7 @@ A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 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. If applicable, add screenshots to help explain your problem.
**Webserver logs** **Webserver logs**
``` ```
If available, post any logs from the web server related to your issue. If available, post any logs from the web server related to your issue.
``` ```
**Relevant information** **Relevant information**
- Host OS of the machine running paperless: [e.g. Archlinux / Ubuntu 20.04]
- Browser [e.g. chrome, safari] - Host OS of the machine running paperless: [e.g. Archlinux / Ubuntu 20.04]
- Version [e.g. 1.0.0] - Browser [e.g. chrome, safari]
- Installation method: [docker / bare metal] - Version [e.g. 1.0.0]
- Any configuration changes you made in `docker-compose.yml`, `docker-compose.env` or `paperless.conf`. - Installation method: [docker / bare metal]
- Any configuration changes you made in `docker-compose.yml`, `docker-compose.env` or `paperless.conf`.

View File

@ -1,10 +1,9 @@
--- ---
name: Other name: Other
about: Anything that is not a feature request or bug. about: Anything that is not a feature request or bug.
title: "[Other] Title of your issue" title: '[Other] Title of your issue'
labels: '' labels: ''
assignees: '' assignees: ''
--- ---
<!-- <!--

View File

@ -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: 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. - 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. - 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. - 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 see the [paperless-ngx merge process](#merging-prs) below.
## Python ## 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: Examples of `non-trivial` PRs might include:
* Additional features - Additional features
* Large changes to many distinct files - Large changes to many distinct files
* Breaking or depreciation of existing features - Breaking or depreciation of existing features
Our community review process for `non-trivial` PRs is the following: 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 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: 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). - 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/ - 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. - 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. 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. 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: The following files need to be changed:
* src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key) - src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key)
* src/paperless/settings.py (in the _LANGUAGES_ array) - src/paperless/settings.py (in the _LANGUAGES_ array)
* src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method) - 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/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. 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 Note that _en-us_ needs to stay on top of the list, as it is the default project language

View File

@ -10,23 +10,23 @@
</p> </p>
<!-- omit in toc --> <!-- omit in toc -->
# Paperless-ngx # 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 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). [#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) - [Features](#features)
- [Getting started](#getting-started) - [Getting started](#getting-started)
- [Contributing](#contributing) - [Contributing](#contributing)
- [Community Support](#community-support) - [Community Support](#community-support)
- [Translation](#translation) - [Translation](#translation)
- [Feature Requests](#feature-requests) - [Feature Requests](#feature-requests)
- [Bugs](#bugs) - [Bugs](#bugs)
- [Affiliated Projects](#affiliated-projects) - [Affiliated Projects](#affiliated-projects)
- [Important Note](#important-note) - [Important Note](#important-note)
@ -35,28 +35,28 @@ A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com)
![Dashboard](https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/_static/screenshots/documents-wchrome.png#gh-light-mode-only) ![Dashboard](https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/_static/screenshots/documents-wchrome.png#gh-light-mode-only)
![Dashboard](https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/_static/screenshots/documents-wchrome-dark.png#gh-dark-mode-only) ![Dashboard](https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docs/_static/screenshots/documents-wchrome-dark.png#gh-dark-mode-only)
* Organize and index your scanned documents with tags, correspondents, types, and more. - 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. - 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). - 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)) - 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. - 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. - Single page application front end.
* Includes a dashboard that shows basic statistics and has document upload. - Includes a dashboard that shows basic statistics and has document upload.
* Filtering by tags, correspondents, types, and more. - Filtering by tags, correspondents, types, and more.
* Customizable views can be saved and displayed on the dashboard. - Customizable views can be saved and displayed on the dashboard.
* Full text search helps you find what you need. - Full text search helps you find what you need.
* Auto completion suggests relevant words from your documents. - Auto completion suggests relevant words from your documents.
* Results are sorted by relevance to your search query. - Results are sorted by relevance to your search query.
* Highlighting shows you which parts of the document matched the query. - Highlighting shows you which parts of the document matched the query.
* Searching for similar documents ("More like this") - Searching for similar documents ("More like this")
* Email processing: Paperless adds documents from your email accounts. - Email processing: Paperless adds documents from your email accounts.
* Configure multiple accounts and filters for each account. - 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. - 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. - 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. - 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. - 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. - 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). - [More screenshots are available in the documentation](https://paperless-ngx.readthedocs.io/en/latest/screenshots.html).
# Getting started # 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. 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 --> <!-- omit in toc -->
### Documentation ### Documentation
The documentation for Paperless-ngx is available on [ReadTheDocs](https://paperless-ngx.readthedocs.io/). 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 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 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. - [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. - [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. 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. 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. - [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) Known issues on Mac: (Could not load reminders and documents)
# Important Note # Important Note

View File

@ -2,18 +2,16 @@
// Protractor configuration file, see link for more information // Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts // 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 } * @type { import("protractor").Config }
*/ */
exports.config = { exports.config = {
allScriptsTimeout: 11000, allScriptsTimeout: 11000,
specs: [ specs: ['./src/**/*.e2e-spec.ts'],
'./src/**/*.e2e-spec.ts'
],
capabilities: { capabilities: {
browserName: 'chrome' browserName: 'chrome',
}, },
directConnect: true, directConnect: true,
baseUrl: 'http://localhost:4200/', baseUrl: 'http://localhost:4200/',
@ -21,16 +19,18 @@ exports.config = {
jasmineNodeOpts: { jasmineNodeOpts: {
showColors: true, showColors: true,
defaultTimeoutInterval: 30000, defaultTimeoutInterval: 30000,
print: function() {} print: function () {},
}, },
onPrepare() { onPrepare() {
require('ts-node').register({ require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json') project: require('path').join(__dirname, './tsconfig.json'),
}); })
jasmine.getEnv().addReporter(new SpecReporter({ jasmine.getEnv().addReporter(
spec: { new SpecReporter({
displayStacktrace: StacktraceOption.PRETTY spec: {
} displayStacktrace: StacktraceOption.PRETTY,
})); },
} })
}; )
},
}

View File

@ -1,23 +1,25 @@
import { AppPage } from './app.po'; import { AppPage } from './app.po'
import { browser, logging } from 'protractor'; import { browser, logging } from 'protractor'
describe('workspace-project App', () => { describe('workspace-project App', () => {
let page: AppPage; let page: AppPage
beforeEach(() => { beforeEach(() => {
page = new AppPage(); page = new AppPage()
}); })
it('should display welcome message', () => { it('should display welcome message', () => {
page.navigateTo(); page.navigateTo()
expect(page.getTitleText()).toEqual('paperless-ui app is running!'); expect(page.getTitleText()).toEqual('paperless-ui app is running!')
}); })
afterEach(async () => { afterEach(async () => {
// Assert that there are no errors emitted from the browser // Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER); const logs = await browser.manage().logs().get(logging.Type.BROWSER)
expect(logs).not.toContain(jasmine.objectContaining({ expect(logs).not.toContain(
level: logging.Level.SEVERE, jasmine.objectContaining({
} as logging.Entry)); level: logging.Level.SEVERE,
}); } as logging.Entry)
}); )
})
})

View File

@ -1,11 +1,13 @@
import { browser, by, element } from 'protractor'; import { browser, by, element } from 'protractor'
export class AppPage { export class AppPage {
navigateTo(): Promise<unknown> { navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl) as Promise<unknown>; return browser.get(browser.baseUrl) as Promise<unknown>
} }
getTitleText(): Promise<string> { getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText() as Promise<string>; return element(
by.css('app-root .content span')
).getText() as Promise<string>
} }
} }

View File

@ -10,15 +10,15 @@ module.exports = function (config) {
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'), require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma') require('@angular-devkit/build-angular/plugins/karma'),
], ],
client: { client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false, // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/paperless-ui'), dir: require('path').join(__dirname, './coverage/paperless-ui'),
reports: ['html', 'lcovonly', 'text-summary'], reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true fixWebpackSourcePaths: true,
}, },
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,
@ -27,6 +27,6 @@ module.exports = function (config) {
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ['Chrome'],
singleRun: false, singleRun: false,
restartOnFileChange: true restartOnFileChange: true,
}); })
}; }

View File

@ -1,39 +1,47 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router'
import { AppFrameComponent } from './components/app-frame/app-frame.component'; import { AppFrameComponent } from './components/app-frame/app-frame.component'
import { DashboardComponent } from './components/dashboard/dashboard.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'; import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
import { DocumentListComponent } from './components/document-list/document-list.component'; import { DocumentListComponent } from './components/document-list/document-list.component'
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'; import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'; import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
import { LogsComponent } from './components/manage/logs/logs.component'; import { LogsComponent } from './components/manage/logs/logs.component'
import { SettingsComponent } from './components/manage/settings/settings.component'; import { SettingsComponent } from './components/manage/settings/settings.component'
import { TagListComponent } from './components/manage/tag-list/tag-list.component'; import { TagListComponent } from './components/manage/tag-list/tag-list.component'
import { NotFoundComponent } from './components/not-found/not-found.component'; import { NotFoundComponent } from './components/not-found/not-found.component'
import {DocumentAsnComponent} from "./components/document-asn/document-asn.component"; import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DirtyFormGuard } from './guards/dirty-form.guard'; import { DirtyFormGuard } from './guards/dirty-form.guard'
const routes: Routes = [ const routes: Routes = [
{path: '', redirectTo: 'dashboard', pathMatch: 'full'}, { path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{path: '', component: AppFrameComponent, children: [ {
{path: 'dashboard', component: DashboardComponent }, path: '',
{path: 'documents', component: DocumentListComponent }, component: AppFrameComponent,
{path: 'view/:id', component: DocumentListComponent }, children: [
{path: 'documents/:id', component: DocumentDetailComponent }, { path: 'dashboard', component: DashboardComponent },
{path: 'asn/:id', component: DocumentAsnComponent }, { path: 'documents', component: DocumentListComponent },
{path: 'tags', component: TagListComponent }, { path: 'view/:id', component: DocumentListComponent },
{path: 'documenttypes', component: DocumentTypeListComponent }, { path: 'documents/:id', component: DocumentDetailComponent },
{path: 'correspondents', component: CorrespondentListComponent }, { path: 'asn/:id', component: DocumentAsnComponent },
{path: 'logs', component: LogsComponent }, { path: 'tags', component: TagListComponent },
{path: 'settings', component: SettingsComponent, canDeactivate: [DirtyFormGuard] }, { path: 'documenttypes', component: DocumentTypeListComponent },
]}, { path: 'correspondents', component: CorrespondentListComponent },
{ path: 'logs', component: LogsComponent },
{
path: 'settings',
component: SettingsComponent,
canDeactivate: [DirtyFormGuard],
},
],
},
{path: '404', component: NotFoundComponent}, { path: '404', component: NotFoundComponent },
{path: '**', redirectTo: '/404', pathMatch: 'full'} { path: '**', redirectTo: '/404', pathMatch: 'full' },
]; ]
@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })], imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
exports: [RouterModule] exports: [RouterModule],
}) })
export class AppRoutingModule { } export class AppRoutingModule {}

View File

@ -1,35 +1,33 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing'
import { AppComponent } from './app.component'; import { AppComponent } from './app.component'
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ imports: [RouterTestingModule],
RouterTestingModule declarations: [AppComponent],
], }).compileComponents()
declarations: [ })
AppComponent
],
}).compileComponents();
});
it('should create the app', () => { it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent)
const app = fixture.componentInstance; const app = fixture.componentInstance
expect(app).toBeTruthy(); expect(app).toBeTruthy()
}); })
it(`should have as title 'paperless-ui'`, () => { it(`should have as title 'paperless-ui'`, () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent)
const app = fixture.componentInstance; const app = fixture.componentInstance
expect(app.title).toEqual('paperless-ui'); expect(app.title).toEqual('paperless-ui')
}); })
it('should render title', () => { it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent)
fixture.detectChanges(); fixture.detectChanges()
const compiled = fixture.nativeElement; const compiled = fixture.nativeElement
expect(compiled.querySelector('.content span').textContent).toContain('paperless-ui app is running!'); expect(compiled.querySelector('.content span').textContent).toContain(
}); 'paperless-ui app is running!'
}); )
})
})

View File

@ -1,24 +1,28 @@
import { SettingsService, SETTINGS_KEYS } from './services/settings.service'; import { SettingsService, SETTINGS_KEYS } from './services/settings.service'
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs'
import { ConsumerStatusService } from './services/consumer-status.service'; import { ConsumerStatusService } from './services/consumer-status.service'
import { ToastService } from './services/toast.service'; import { ToastService } from './services/toast.service'
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss'],
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit, OnDestroy {
newDocumentSubscription: Subscription
successSubscription: Subscription
failedSubscription: Subscription
newDocumentSubscription: Subscription; constructor(
successSubscription: Subscription; private settings: SettingsService,
failedSubscription: Subscription; private consumerStatusService: ConsumerStatusService,
private toastService: ToastService,
constructor (private settings: SettingsService, private consumerStatusService: ConsumerStatusService, private toastService: ToastService, private router: Router) { private router: Router
let anyWindow = (window as any) ) {
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'; let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
this.settings.updateAppearanceSettings() this.settings.updateAppearanceSettings()
} }
@ -36,7 +40,12 @@ export class AppComponent implements OnInit, OnDestroy {
} }
private showNotification(key) { private showNotification(key) {
if (this.router.url == '/dashboard' && this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD)) { if (
this.router.url == '/dashboard' &&
this.settings.get(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD
)
) {
return false return false
} }
return this.settings.get(key) return this.settings.get(key)
@ -45,26 +54,50 @@ export class AppComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.consumerStatusService.connect() this.consumerStatusService.connect()
this.successSubscription = this.consumerStatusService
.onDocumentConsumptionFinished()
.subscribe((status) => {
if (
this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)
) {
this.toastService.show({
title: $localize`Document added`,
delay: 10000,
content: $localize`Document ${status.filename} was added to paperless.`,
actionName: $localize`Open document`,
action: () => {
this.router.navigate(['documents', status.documentId])
},
})
}
})
this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { this.failedSubscription = this.consumerStatusService
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)) { .onDocumentConsumptionFailed()
this.toastService.show({title: $localize`Document added`, delay: 10000, content: $localize`Document ${status.filename} was added to paperless.`, actionName: $localize`Open document`, action: () => { .subscribe((status) => {
this.router.navigate(['documents', status.documentId]) if (
}}) this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)
} ) {
}) this.toastService.showError(
$localize`Could not add ${status.filename}\: ${status.message}`
)
}
})
this.failedSubscription = this.consumerStatusService.onDocumentConsumptionFailed().subscribe(status => { this.newDocumentSubscription = this.consumerStatusService
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED)) { .onDocumentDetected()
this.toastService.showError($localize`Could not add ${status.filename}\: ${status.message}`) .subscribe((status) => {
} if (
}) this.showNotification(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT
this.newDocumentSubscription = this.consumerStatusService.onDocumentDetected().subscribe(status => { )
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT)) { ) {
this.toastService.show({title: $localize`New document detected`, delay: 5000, content: $localize`Document ${status.filename} is being processed by paperless.`}) this.toastService.show({
} title: $localize`New document detected`,
}) delay: 5000,
content: $localize`Document ${status.filename} is being processed by paperless.`,
})
}
})
} }
} }

View File

@ -1,85 +1,88 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'; import { AppComponent } from './app.component'
import { NgbDateAdapter, NgbDateParserFormatter, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import {
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; NgbDateAdapter,
import { DocumentListComponent } from './components/document-list/document-list.component'; NgbDateParserFormatter,
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'; NgbModule,
import { DashboardComponent } from './components/dashboard/dashboard.component'; } from '@ng-bootstrap/ng-bootstrap'
import { TagListComponent } from './components/manage/tag-list/tag-list.component'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'; import { DocumentListComponent } from './components/document-list/document-list.component'
import { LogsComponent } from './components/manage/logs/logs.component'; import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
import { SettingsComponent } from './components/manage/settings/settings.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TagListComponent } from './components/manage/tag-list/tag-list.component'
import { DatePipe, registerLocaleData } from '@angular/common'; import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
import { NotFoundComponent } from './components/not-found/not-found.component'; import { LogsComponent } from './components/manage/logs/logs.component'
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'; import { SettingsComponent } from './components/manage/settings/settings.component'
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'; import { DatePipe, registerLocaleData } from '@angular/common'
import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'; import { NotFoundComponent } from './components/not-found/not-found.component'
import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'; import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
import { TagComponent } from './components/common/tag/tag.component'; import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
import { PageHeaderComponent } from './components/common/page-header/page-header.component'; import { CorrespondentEditDialogComponent } from './components/manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'; import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
import { ToastsComponent } from './components/common/toasts/toasts.component'; import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'; import { TagComponent } from './components/common/tag/tag.component'
import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component'; import { PageHeaderComponent } from './components/common/page-header/page-header.component'
import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; import { AppFrameComponent } from './components/app-frame/app-frame.component'
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'; import { ToastsComponent } from './components/common/toasts/toasts.component'
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'; import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'; import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component'
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'; import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { NgxFileDropModule } from 'ngx-file-drop'; import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'
import { TextComponent } from './components/common/input/text/text.component'; import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'
import { SelectComponent } from './components/common/input/select/select.component'; import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'
import { CheckComponent } from './components/common/input/check/check.component'; import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'; import { NgxFileDropModule } from 'ngx-file-drop'
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { TextComponent } from './components/common/input/text/text.component'
import { TagsComponent } from './components/common/input/tags/tags.component'; import { SelectComponent } from './components/common/input/select/select.component'
import { SortableDirective } from './directives/sortable.directive'; import { CheckComponent } from './components/common/input/check/check.component'
import { CookieService } from 'ngx-cookie-service'; import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
import { CsrfInterceptor } from './interceptors/csrf.interceptor'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'; import { TagsComponent } from './components/common/input/tags/tags.component'
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'; import { SortableDirective } from './directives/sortable.directive'
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'; import { CookieService } from 'ngx-cookie-service'
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'; import { CsrfInterceptor } from './interceptors/csrf.interceptor'
import { PdfViewerModule } from 'ng2-pdf-viewer'; import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'; import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'
import { YesNoPipe } from './pipes/yes-no.pipe'; import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'
import { FileSizePipe } from './pipes/file-size.pipe'; import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
import { FilterPipe } from './pipes/filter.pipe'; import { PdfViewerModule } from 'ng2-pdf-viewer'
import { DocumentTitlePipe } from './pipes/document-title.pipe'; import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'; import { YesNoPipe } from './pipes/yes-no.pipe'
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'; import { FileSizePipe } from './pipes/file-size.pipe'
import { NgSelectModule } from '@ng-select/ng-select'; import { FilterPipe } from './pipes/filter.pipe'
import { NumberComponent } from './components/common/input/number/number.component'; import { DocumentTitlePipe } from './pipes/document-title.pipe'
import { SafePipe } from './pipes/safe.pipe'; import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'
import { CustomDatePipe } from './pipes/custom-date.pipe'; import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
import { DateComponent } from './components/common/input/date/date.component'; import { NgSelectModule } from '@ng-select/ng-select'
import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter'; import { NumberComponent } from './components/common/input/number/number.component'
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'; import { SafePipe } from './pipes/safe.pipe'
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'; import { CustomDatePipe } from './pipes/custom-date.pipe'
import { ColorSliderModule } from 'ngx-color/slider'; import { DateComponent } from './components/common/input/date/date.component'
import { ColorComponent } from './components/common/input/color/color.component'; import { ISODateTimeAdapter } from './utils/ngb-iso-date-time-adapter'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'; import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'
import localeCs from '@angular/common/locales/cs'; import { ColorSliderModule } from 'ngx-color/slider'
import localeDa from '@angular/common/locales/da'; import { ColorComponent } from './components/common/input/color/color.component'
import localeDe from '@angular/common/locales/de'; import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import localeEnGb from '@angular/common/locales/en-GB';
import localeEs from '@angular/common/locales/es';
import localeFr from '@angular/common/locales/fr';
import localeIt from '@angular/common/locales/it';
import localeLb from '@angular/common/locales/lb';
import localeNl from '@angular/common/locales/nl';
import localePl from '@angular/common/locales/pl';
import localePt from '@angular/common/locales/pt';
import localeSv from '@angular/common/locales/sv';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeCs from '@angular/common/locales/cs'
import localeDa from '@angular/common/locales/da'
import localeDe from '@angular/common/locales/de'
import localeEnGb from '@angular/common/locales/en-GB'
import localeEs from '@angular/common/locales/es'
import localeFr from '@angular/common/locales/fr'
import localeIt from '@angular/common/locales/it'
import localeLb from '@angular/common/locales/lb'
import localeNl from '@angular/common/locales/nl'
import localePl from '@angular/common/locales/pl'
import localePt from '@angular/common/locales/pt'
import localeSv from '@angular/common/locales/sv'
import localeRo from '@angular/common/locales/ro'
import localeRu from '@angular/common/locales/ru'
registerLocaleData(localeCs) registerLocaleData(localeCs)
registerLocaleData(localeDa) registerLocaleData(localeDa)
@ -91,8 +94,8 @@ registerLocaleData(localeIt)
registerLocaleData(localeLb) registerLocaleData(localeLb)
registerLocaleData(localeNl) registerLocaleData(localeNl)
registerLocaleData(localePl) registerLocaleData(localePl)
registerLocaleData(localePt, "pt-BR") registerLocaleData(localePt, 'pt-BR')
registerLocaleData(localePt, "pt-PT") registerLocaleData(localePt, 'pt-PT')
registerLocaleData(localeRo) registerLocaleData(localeRo)
registerLocaleData(localeRu) registerLocaleData(localeRu)
registerLocaleData(localeSv) registerLocaleData(localeSv)
@ -146,7 +149,7 @@ registerLocaleData(localeSv)
CustomDatePipe, CustomDatePipe,
DateComponent, DateComponent,
ColorComponent, ColorComponent,
DocumentAsnComponent DocumentAsnComponent,
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -159,24 +162,26 @@ registerLocaleData(localeSv)
InfiniteScrollModule, InfiniteScrollModule,
PdfViewerModule, PdfViewerModule,
NgSelectModule, NgSelectModule,
ColorSliderModule ColorSliderModule,
], ],
providers: [ providers: [
DatePipe, DatePipe,
CookieService, { CookieService,
{
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: CsrfInterceptor, useClass: CsrfInterceptor,
multi: true multi: true,
},{ },
{
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: ApiVersionInterceptor, useClass: ApiVersionInterceptor,
multi: true multi: true,
}, },
FilterPipe, FilterPipe,
DocumentTitlePipe, DocumentTitlePipe,
{provide: NgbDateAdapter, useClass: ISODateTimeAdapter}, { provide: NgbDateAdapter, useClass: ISODateTimeAdapter },
{provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter} { provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule {}

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { AppFrameComponent } from './app-frame.component'; import { AppFrameComponent } from './app-frame.component'
describe('AppFrameComponent', () => { describe('AppFrameComponent', () => {
let component: AppFrameComponent; let component: AppFrameComponent
let fixture: ComponentFixture<AppFrameComponent>; let fixture: ComponentFixture<AppFrameComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ AppFrameComponent ] declarations: [AppFrameComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AppFrameComponent); fixture = TestBed.createComponent(AppFrameComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,26 +1,31 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms'
import { ActivatedRoute, Router, Params } from '@angular/router'; import { ActivatedRoute, Router, Params } from '@angular/router'
import { from, Observable, Subscription, BehaviorSubject } from 'rxjs'; import { from, Observable, Subscription, BehaviorSubject } from 'rxjs'
import { debounceTime, distinctUntilChanged, map, switchMap, first } from 'rxjs/operators'; import {
import { PaperlessDocument } from 'src/app/data/paperless-document'; debounceTime,
import { OpenDocumentsService } from 'src/app/services/open-documents.service'; distinctUntilChanged,
import { SavedViewService } from 'src/app/services/rest/saved-view.service'; map,
import { SearchService } from 'src/app/services/rest/search.service'; switchMap,
import { environment } from 'src/environments/environment'; first,
import { DocumentDetailComponent } from '../document-detail/document-detail.component'; } from 'rxjs/operators'
import { Meta } from '@angular/platform-browser'; import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'; import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SearchService } from 'src/app/services/rest/search.service'
import { environment } from 'src/environments/environment'
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import { Meta } from '@angular/platform-browser'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'
@Component({ @Component({
selector: 'app-app-frame', selector: 'app-app-frame',
templateUrl: './app-frame.component.html', templateUrl: './app-frame.component.html',
styleUrls: ['./app-frame.component.scss'] styleUrls: ['./app-frame.component.scss'],
}) })
export class AppFrameComponent { export class AppFrameComponent {
constructor(
constructor (
public router: Router, public router: Router,
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private openDocumentsService: OpenDocumentsService, private openDocumentsService: OpenDocumentsService,
@ -28,7 +33,7 @@ export class AppFrameComponent {
public savedViewService: SavedViewService, public savedViewService: SavedViewService,
private list: DocumentListViewService, private list: DocumentListViewService,
private meta: Meta private meta: Meta
) { } ) {}
versionString = `${environment.appTitle} ${environment.version}` versionString = `${environment.appTitle} ${environment.version}`
@ -48,14 +53,14 @@ export class AppFrameComponent {
text$.pipe( text$.pipe(
debounceTime(200), debounceTime(200),
distinctUntilChanged(), distinctUntilChanged(),
map(term => { map((term) => {
if (term.lastIndexOf(' ') != -1) { if (term.lastIndexOf(' ') != -1) {
return term.substring(term.lastIndexOf(' ') + 1) return term.substring(term.lastIndexOf(' ') + 1)
} else { } else {
return term return term
} }
}), }),
switchMap(term => switchMap((term) =>
term.length < 2 ? from([[]]) : this.searchService.autocomplete(term) term.length < 2 ? from([[]]) : this.searchService.autocomplete(term)
) )
) )
@ -66,49 +71,60 @@ export class AppFrameComponent {
let lastSpaceIndex = currentSearch.lastIndexOf(' ') let lastSpaceIndex = currentSearch.lastIndexOf(' ')
if (lastSpaceIndex != -1) { if (lastSpaceIndex != -1) {
currentSearch = currentSearch.substring(0, lastSpaceIndex + 1) currentSearch = currentSearch.substring(0, lastSpaceIndex + 1)
currentSearch += event.item + " " currentSearch += event.item + ' '
} else { } else {
currentSearch = event.item + " " currentSearch = event.item + ' '
} }
this.searchField.patchValue(currentSearch) this.searchField.patchValue(currentSearch)
} }
search() { search() {
this.closeMenu() this.closeMenu()
this.list.quickFilter([{rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value}]) this.list.quickFilter([
{ rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value },
])
} }
closeDocument(d: PaperlessDocument) { closeDocument(d: PaperlessDocument) {
this.openDocumentsService.closeDocument(d).pipe(first()).subscribe(confirmed => { this.openDocumentsService
if (confirmed) { .closeDocument(d)
this.closeMenu() .pipe(first())
let route = this.activatedRoute.snapshot .subscribe((confirmed) => {
while (route.firstChild) { if (confirmed) {
route = route.firstChild this.closeMenu()
let route = this.activatedRoute.snapshot
while (route.firstChild) {
route = route.firstChild
}
if (
route.component == DocumentDetailComponent &&
route.params['id'] == d.id
) {
this.router.navigate([''])
}
} }
if (route.component == DocumentDetailComponent && route.params['id'] == d.id) { })
this.router.navigate([""])
}
}
})
} }
closeAll() { closeAll() {
// user may need to confirm losing unsaved changes // user may need to confirm losing unsaved changes
this.openDocumentsService.closeAll().pipe(first()).subscribe(confirmed => { this.openDocumentsService
if (confirmed) { .closeAll()
this.closeMenu() .pipe(first())
.subscribe((confirmed) => {
if (confirmed) {
this.closeMenu()
// TODO: is there a better way to do this? // TODO: is there a better way to do this?
let route = this.activatedRoute let route = this.activatedRoute
while (route.firstChild) { while (route.firstChild) {
route = route.firstChild route = route.firstChild
}
if (route.component === DocumentDetailComponent) {
this.router.navigate([''])
}
} }
if (route.component === DocumentDetailComponent) { })
this.router.navigate([""])
}
}
})
} }
get displayName() { get displayName() {
@ -123,5 +139,4 @@ export class AppFrameComponent {
return null return null
} }
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ConfirmDialogComponent } from './confirm-dialog.component'; import { ConfirmDialogComponent } from './confirm-dialog.component'
describe('ConfirmDialogComponent', () => { describe('ConfirmDialogComponent', () => {
let component: ConfirmDialogComponent; let component: ConfirmDialogComponent
let fixture: ComponentFixture<ConfirmDialogComponent>; let fixture: ComponentFixture<ConfirmDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ConfirmDialogComponent ] declarations: [ConfirmDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ConfirmDialogComponent); fixture = TestBed.createComponent(ConfirmDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,14 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject } from 'rxjs'; import { Subject } from 'rxjs'
@Component({ @Component({
selector: 'app-confirm-dialog', selector: 'app-confirm-dialog',
templateUrl: './confirm-dialog.component.html', templateUrl: './confirm-dialog.component.html',
styleUrls: ['./confirm-dialog.component.scss'] styleUrls: ['./confirm-dialog.component.scss'],
}) })
export class ConfirmDialogComponent { export class ConfirmDialogComponent {
constructor(public activeModal: NgbActiveModal) {}
constructor(public activeModal: NgbActiveModal) { }
@Output() @Output()
public confirmClicked = new EventEmitter() public confirmClicked = new EventEmitter()
@ -24,7 +23,7 @@ export class ConfirmDialogComponent {
message message
@Input() @Input()
btnClass = "btn-primary" btnClass = 'btn-primary'
@Input() @Input()
btnCaption = $localize`Confirm` btnCaption = $localize`Confirm`

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DateDropdownComponent } from './date-dropdown.component'; import { DateDropdownComponent } from './date-dropdown.component'
describe('DateDropdownComponent', () => { describe('DateDropdownComponent', () => {
let component: DateDropdownComponent; let component: DateDropdownComponent
let fixture: ComponentFixture<DateDropdownComponent>; let fixture: ComponentFixture<DateDropdownComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DateDropdownComponent ] declarations: [DateDropdownComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DateDropdownComponent); fixture = TestBed.createComponent(DateDropdownComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,10 +1,17 @@
import { formatDate } from '@angular/common'; import { formatDate } from '@angular/common'
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core'; import {
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; Component,
import { Subject, Subscription } from 'rxjs'; EventEmitter,
import { debounceTime } from 'rxjs/operators'; Input,
import { SettingsService } from 'src/app/services/settings.service'; Output,
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'; OnInit,
OnDestroy,
} from '@angular/core'
import { NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'
import { Subject, Subscription } from 'rxjs'
import { debounceTime } from 'rxjs/operators'
import { SettingsService } from 'src/app/services/settings.service'
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
export interface DateSelection { export interface DateSelection {
before?: string before?: string
@ -20,21 +27,18 @@ const LAST_YEAR = 3
selector: 'app-date-dropdown', selector: 'app-date-dropdown',
templateUrl: './date-dropdown.component.html', templateUrl: './date-dropdown.component.html',
styleUrls: ['./date-dropdown.component.scss'], styleUrls: ['./date-dropdown.component.scss'],
providers: [ providers: [{ provide: NgbDateAdapter, useClass: ISODateAdapter }],
{provide: NgbDateAdapter, useClass: ISODateAdapter},
]
}) })
export class DateDropdownComponent implements OnInit, OnDestroy { export class DateDropdownComponent implements OnInit, OnDestroy {
constructor(settings: SettingsService) { constructor(settings: SettingsService) {
this.datePlaceHolder = settings.getLocalizedDateInputFormat() this.datePlaceHolder = settings.getLocalizedDateInputFormat()
} }
quickFilters = [ quickFilters = [
{id: LAST_7_DAYS, name: $localize`Last 7 days`}, { id: LAST_7_DAYS, name: $localize`Last 7 days` },
{id: LAST_MONTH, name: $localize`Last month`}, { id: LAST_MONTH, name: $localize`Last month` },
{id: LAST_3_MONTHS, name: $localize`Last 3 months`}, { id: LAST_3_MONTHS, name: $localize`Last 3 months` },
{id: LAST_YEAR, name: $localize`Last year`} { id: LAST_YEAR, name: $localize`Last year` },
] ]
datePlaceHolder: string datePlaceHolder: string
@ -62,9 +66,7 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
private sub: Subscription private sub: Subscription
ngOnInit() { ngOnInit() {
this.sub = this.datesSetDebounce$.pipe( this.sub = this.datesSetDebounce$.pipe(debounceTime(400)).subscribe(() => {
debounceTime(400)
).subscribe(() => {
this.onChange() this.onChange()
}) })
} }
@ -81,11 +83,11 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
switch (qf) { switch (qf) {
case LAST_7_DAYS: case LAST_7_DAYS:
date.setDate(date.getDate() - 7) date.setDate(date.getDate() - 7)
break; break
case LAST_MONTH: case LAST_MONTH:
date.setMonth(date.getMonth() - 1) date.setMonth(date.getMonth() - 1)
break; break
case LAST_3_MONTHS: case LAST_3_MONTHS:
date.setMonth(date.getMonth() - 3) date.setMonth(date.getMonth() - 3)
@ -94,20 +96,22 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
case LAST_YEAR: case LAST_YEAR:
date.setFullYear(date.getFullYear() - 1) date.setFullYear(date.getFullYear() - 1)
break break
}
} this.dateAfter = formatDate(date, 'yyyy-MM-dd', 'en-us', 'UTC')
this.dateAfter = formatDate(date, 'yyyy-MM-dd', "en-us", "UTC")
this.onChange() this.onChange()
} }
onChange() { onChange() {
this.dateAfterChange.emit(this.dateAfter) this.dateAfterChange.emit(this.dateAfter)
this.dateBeforeChange.emit(this.dateBefore) this.dateBeforeChange.emit(this.dateBefore)
this.datesSet.emit({after: this.dateAfter, before: this.dateBefore}) this.datesSet.emit({ after: this.dateAfter, before: this.dateBefore })
} }
onChangeDebounce() { onChangeDebounce() {
this.datesSetDebounce$.next({after: this.dateAfter, before: this.dateBefore}) this.datesSetDebounce$.next({
after: this.dateAfter,
before: this.dateBefore,
})
} }
clearBefore() { clearBefore() {

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { EditDialogComponent } from './edit-dialog.component'; import { EditDialogComponent } from './edit-dialog.component'
describe('EditDialogComponent', () => { describe('EditDialogComponent', () => {
let component: EditDialogComponent; let component: EditDialogComponent
let fixture: ComponentFixture<EditDialogComponent>; let fixture: ComponentFixture<EditDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ EditDialogComponent ] declarations: [EditDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(EditDialogComponent); fixture = TestBed.createComponent(EditDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,20 +1,22 @@
import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { Observable } from 'rxjs'; import { Observable } from 'rxjs'
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators'
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'; import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
import { ObjectWithId } from 'src/app/data/object-with-id'; import { ObjectWithId } from 'src/app/data/object-with-id'
import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'; import { AbstractPaperlessService } from 'src/app/services/rest/abstract-paperless-service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
@Directive() @Directive()
export abstract class EditDialogComponent<T extends ObjectWithId> implements OnInit { export abstract class EditDialogComponent<T extends ObjectWithId>
implements OnInit
{
constructor( constructor(
private service: AbstractPaperlessService<T>, private service: AbstractPaperlessService<T>,
private activeModal: NgbActiveModal, private activeModal: NgbActiveModal,
private toastService: ToastService) { } private toastService: ToastService
) {}
@Input() @Input()
dialogMode: string = 'create' dialogMode: string = 'create'
@ -43,7 +45,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
// wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM // wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM
setTimeout(() => { setTimeout(() => {
this.closeEnabled = true this.closeEnabled = true
}); })
} }
getCreateTitle() { getCreateTitle() {
@ -65,7 +67,7 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
case 'edit': case 'edit':
return this.getEditTitle() return this.getEditTitle()
default: default:
break; break
} }
} }
@ -78,25 +80,31 @@ export abstract class EditDialogComponent<T extends ObjectWithId> implements OnI
} }
save() { save() {
var newObject = Object.assign(Object.assign({}, this.object), this.objectForm.value) var newObject = Object.assign(
Object.assign({}, this.object),
this.objectForm.value
)
var serverResponse: Observable<T> var serverResponse: Observable<T>
switch (this.dialogMode) { switch (this.dialogMode) {
case 'create': case 'create':
serverResponse = this.service.create(newObject) serverResponse = this.service.create(newObject)
break; break
case 'edit': case 'edit':
serverResponse = this.service.update(newObject) serverResponse = this.service.update(newObject)
default: default:
break; break
} }
this.networkActive = true this.networkActive = true
serverResponse.subscribe(result => { serverResponse.subscribe(
this.activeModal.close() (result) => {
this.success.emit(result) this.activeModal.close()
}, error => { this.success.emit(result)
this.error = error.error },
this.networkActive = false (error) => {
}) this.error = error.error
this.networkActive = false
}
)
} }
cancel() { cancel() {

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FilterableDropodownComponent } from './filterable-dropdown.component'; import { FilterableDropodownComponent } from './filterable-dropdown.component'
describe('FilterableDropodownComponent', () => { describe('FilterableDropodownComponent', () => {
let component: FilterableDropodownComponent; let component: FilterableDropodownComponent
let fixture: ComponentFixture<FilterableDropodownComponent>; let fixture: ComponentFixture<FilterableDropodownComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ FilterableDropodownComponent ] declarations: [FilterableDropodownComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FilterableDropodownComponent); fixture = TestBed.createComponent(FilterableDropodownComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,17 +1,23 @@
import { Component, EventEmitter, Input, Output, ElementRef, ViewChild } from '@angular/core'; import {
import { FilterPipe } from 'src/app/pipes/filter.pipe'; Component,
EventEmitter,
Input,
Output,
ElementRef,
ViewChild,
} from '@angular/core'
import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component'; import { ToggleableItemState } from './toggleable-dropdown-button/toggleable-dropdown-button.component'
import { MatchingModel } from 'src/app/data/matching-model'; import { MatchingModel } from 'src/app/data/matching-model'
import { Subject } from 'rxjs'; import { Subject } from 'rxjs'
export interface ChangedItems { export interface ChangedItems {
itemsToAdd: MatchingModel[], itemsToAdd: MatchingModel[]
itemsToRemove: MatchingModel[] itemsToRemove: MatchingModel[]
} }
export class FilterableDropdownSelectionModel { export class FilterableDropdownSelectionModel {
changed = new Subject<FilterableDropdownSelectionModel>() changed = new Subject<FilterableDropdownSelectionModel>()
multiple = false multiple = false
@ -22,14 +28,20 @@ export class FilterableDropdownSelectionModel {
get itemsSorted(): MatchingModel[] { get itemsSorted(): MatchingModel[] {
// TODO: this is getting called very often // TODO: this is getting called very often
return this.items.sort((a,b) => { return this.items.sort((a, b) => {
if (a.id == null && b.id != null) { if (a.id == null && b.id != null) {
return -1 return -1
} else if (a.id != null && b.id == null) { } else if (a.id != null && b.id == null) {
return 1 return 1
} else if (this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && this.getNonTemporary(b.id) != ToggleableItemState.NotSelected) { } else if (
this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
this.getNonTemporary(b.id) != ToggleableItemState.NotSelected
) {
return 1 return 1
} else if (this.getNonTemporary(a.id) != ToggleableItemState.NotSelected && this.getNonTemporary(b.id) == ToggleableItemState.NotSelected) { } else if (
this.getNonTemporary(a.id) != ToggleableItemState.NotSelected &&
this.getNonTemporary(b.id) == ToggleableItemState.NotSelected
) {
return -1 return -1
} else { } else {
return a.name.localeCompare(b.name) return a.name.localeCompare(b.name)
@ -42,11 +54,17 @@ export class FilterableDropdownSelectionModel {
private temporarySelectionStates = new Map<number, ToggleableItemState>() private temporarySelectionStates = new Map<number, ToggleableItemState>()
getSelectedItems() { getSelectedItems() {
return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected) return this.items.filter(
(i) =>
this.temporarySelectionStates.get(i.id) == ToggleableItemState.Selected
)
} }
getExcludedItems() { getExcludedItems() {
return this.items.filter(i => this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded) return this.items.filter(
(i) =>
this.temporarySelectionStates.get(i.id) == ToggleableItemState.Excluded
)
} }
set(id: number, state: ToggleableItemState, fireEvent = true) { set(id: number, state: ToggleableItemState, fireEvent = true) {
@ -62,9 +80,16 @@ export class FilterableDropdownSelectionModel {
toggle(id: number, fireEvent = true) { toggle(id: number, fireEvent = true) {
let state = this.temporarySelectionStates.get(id) let state = this.temporarySelectionStates.get(id)
if (state == null || (state != ToggleableItemState.Selected && state != ToggleableItemState.Excluded)) { if (
state == null ||
(state != ToggleableItemState.Selected &&
state != ToggleableItemState.Excluded)
) {
this.temporarySelectionStates.set(id, ToggleableItemState.Selected) this.temporarySelectionStates.set(id, ToggleableItemState.Selected)
} else if (state == ToggleableItemState.Selected || state == ToggleableItemState.Excluded) { } else if (
state == ToggleableItemState.Selected ||
state == ToggleableItemState.Excluded
) {
this.temporarySelectionStates.delete(id) this.temporarySelectionStates.delete(id)
} }
@ -91,7 +116,7 @@ export class FilterableDropdownSelectionModel {
} }
} }
exclude(id: number, fireEvent:boolean = true) { exclude(id: number, fireEvent: boolean = true) {
let state = this.temporarySelectionStates.get(id) let state = this.temporarySelectionStates.get(id)
if (state == null || state != ToggleableItemState.Excluded) { if (state == null || state != ToggleableItemState.Excluded) {
this.temporarySelectionStates.set(id, ToggleableItemState.Excluded) this.temporarySelectionStates.set(id, ToggleableItemState.Excluded)
@ -130,7 +155,9 @@ export class FilterableDropdownSelectionModel {
} }
get(id: number) { get(id: number) {
return this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected return (
this.temporarySelectionStates.get(id) || ToggleableItemState.NotSelected
)
} }
selectionSize() { selectionSize() {
@ -150,9 +177,19 @@ export class FilterableDropdownSelectionModel {
} }
isDirty() { isDirty() {
if (!Array.from(this.temporarySelectionStates.keys()).every(id => this.temporarySelectionStates.get(id) == this.selectionStates.get(id))) { if (
!Array.from(this.temporarySelectionStates.keys()).every(
(id) =>
this.temporarySelectionStates.get(id) == this.selectionStates.get(id)
)
) {
return true return true
} else if (!Array.from(this.selectionStates.keys()).every(id => this.selectionStates.get(id) == this.temporarySelectionStates.get(id))) { } else if (
!Array.from(this.selectionStates.keys()).every(
(id) =>
this.selectionStates.get(id) == this.temporarySelectionStates.get(id)
)
) {
return true return true
} else if (this.temporaryLogicalOperator !== this._logicalOperator) { } else if (this.temporaryLogicalOperator !== this._logicalOperator) {
return true return true
@ -162,7 +199,10 @@ export class FilterableDropdownSelectionModel {
} }
isNoneSelected() { isNoneSelected() {
return this.selectionSize() == 1 && this.get(null) == ToggleableItemState.Selected return (
this.selectionSize() == 1 &&
this.get(null) == ToggleableItemState.Selected
)
} }
init(map) { init(map) {
@ -187,8 +227,17 @@ export class FilterableDropdownSelectionModel {
diff(): ChangedItems { diff(): ChangedItems {
return { return {
itemsToAdd: this.items.filter(item => this.temporarySelectionStates.get(item.id) == ToggleableItemState.Selected && this.selectionStates.get(item.id) != ToggleableItemState.Selected), itemsToAdd: this.items.filter(
itemsToRemove: this.items.filter(item => !this.temporarySelectionStates.has(item.id) && this.selectionStates.has(item.id)), (item) =>
this.temporarySelectionStates.get(item.id) ==
ToggleableItemState.Selected &&
this.selectionStates.get(item.id) != ToggleableItemState.Selected
),
itemsToRemove: this.items.filter(
(item) =>
!this.temporarySelectionStates.has(item.id) &&
this.selectionStates.has(item.id)
),
} }
} }
} }
@ -196,10 +245,9 @@ export class FilterableDropdownSelectionModel {
@Component({ @Component({
selector: 'app-filterable-dropdown', selector: 'app-filterable-dropdown',
templateUrl: './filterable-dropdown.component.html', templateUrl: './filterable-dropdown.component.html',
styleUrls: ['./filterable-dropdown.component.scss'] styleUrls: ['./filterable-dropdown.component.scss'],
}) })
export class FilterableDropdownComponent { export class FilterableDropdownComponent {
@ViewChild('listFilterTextInput') listFilterTextInput: ElementRef @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
@ViewChild('dropdown') dropdown: NgbDropdown @ViewChild('dropdown') dropdown: NgbDropdown
@ -211,7 +259,7 @@ export class FilterableDropdownComponent {
this._selectionModel.items = Array.from(items) this._selectionModel.items = Array.from(items)
this._selectionModel.items.unshift({ this._selectionModel.items.unshift({
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`, name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
id: null id: null,
}) })
} }
} }
@ -229,7 +277,7 @@ export class FilterableDropdownComponent {
model.items = this.selectionModel.items model.items = this.selectionModel.items
model.multiple = this.selectionModel.multiple model.multiple = this.selectionModel.multiple
} }
model.changed.subscribe(updatedModel => { model.changed.subscribe((updatedModel) => {
this.selectionModelChange.next(updatedModel) this.selectionModelChange.next(updatedModel)
}) })
this._selectionModel = model this._selectionModel = model
@ -255,7 +303,7 @@ export class FilterableDropdownComponent {
title: string title: string
@Input() @Input()
filterPlaceholder: string = "" filterPlaceholder: string = ''
@Input() @Input()
icon: string icon: string
@ -276,14 +324,17 @@ export class FilterableDropdownComponent {
open = new EventEmitter() open = new EventEmitter()
get operatorToggleEnabled(): boolean { get operatorToggleEnabled(): boolean {
return this.selectionModel.selectionSize() > 1 && this.selectionModel.getExcludedItems().length == 0 return (
this.selectionModel.selectionSize() > 1 &&
this.selectionModel.getExcludedItems().length == 0
)
} }
modelIsDirty: boolean = false modelIsDirty: boolean = false
constructor(private filterPipe: FilterPipe) { constructor(private filterPipe: FilterPipe) {
this.selectionModel = new FilterableDropdownSelectionModel() this.selectionModel = new FilterableDropdownSelectionModel()
this.selectionModelChange.subscribe(updatedModel => { this.selectionModelChange.subscribe((updatedModel) => {
this.modelIsDirty = updatedModel.isDirty() this.modelIsDirty = updatedModel.isDirty()
}) })
} }
@ -300,7 +351,7 @@ export class FilterableDropdownComponent {
dropdownOpenChange(open: boolean): void { dropdownOpenChange(open: boolean): void {
if (open) { if (open) {
setTimeout(() => { setTimeout(() => {
this.listFilterTextInput.nativeElement.focus(); this.listFilterTextInput.nativeElement.focus()
}, 0) }, 0)
if (this.editing) { if (this.editing) {
this.selectionModel.reset() this.selectionModel.reset()

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component'; import { ToggleableDropdownButtonComponent } from './toggleable-dropdown-button.component'
describe('ToggleableDropdownButtonComponent', () => { describe('ToggleableDropdownButtonComponent', () => {
let component: ToggleableDropdownButtonComponent; let component: ToggleableDropdownButtonComponent
let fixture: ComponentFixture<ToggleableDropdownButtonComponent>; let fixture: ComponentFixture<ToggleableDropdownButtonComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ToggleableDropdownButtonComponent ] declarations: [ToggleableDropdownButtonComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ToggleableDropdownButtonComponent); fixture = TestBed.createComponent(ToggleableDropdownButtonComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,20 +1,19 @@
import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'; import { Component, EventEmitter, Input, Output, OnInit } from '@angular/core'
import { MatchingModel } from 'src/app/data/matching-model'; import { MatchingModel } from 'src/app/data/matching-model'
export enum ToggleableItemState { export enum ToggleableItemState {
NotSelected = 0, NotSelected = 0,
Selected = 1, Selected = 1,
PartiallySelected = 2, PartiallySelected = 2,
Excluded = 3 Excluded = 3,
} }
@Component({ @Component({
selector: 'app-toggleable-dropdown-button', selector: 'app-toggleable-dropdown-button',
templateUrl: './toggleable-dropdown-button.component.html', templateUrl: './toggleable-dropdown-button.component.html',
styleUrls: ['./toggleable-dropdown-button.component.scss'] styleUrls: ['./toggleable-dropdown-button.component.scss'],
}) })
export class ToggleableDropdownButtonComponent { export class ToggleableDropdownButtonComponent {
@Input() @Input()
item: MatchingModel item: MatchingModel

View File

@ -1,30 +1,29 @@
import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; import { Directive, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
import { ControlValueAccessor } from '@angular/forms'; import { ControlValueAccessor } from '@angular/forms'
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid'
@Directive() @Directive()
export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor { export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
@ViewChild('inputField')
@ViewChild("inputField")
inputField: ElementRef inputField: ElementRef
constructor() { } constructor() {}
onChange = (newValue: T) => {}; onChange = (newValue: T) => {}
onTouched = () => {}; onTouched = () => {}
writeValue(newValue: any): void { writeValue(newValue: any): void {
this.value = newValue this.value = newValue
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.onChange = fn; this.onChange = fn
} }
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
this.onTouched = fn; this.onTouched = fn
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled
} }
focus() { focus() {
@ -37,7 +36,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
title: string title: string
@Input() @Input()
disabled = false; disabled = false
@Input() @Input()
error: string error: string

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { CheckComponent } from './check.component'; import { CheckComponent } from './check.component'
describe('CheckComponent', () => { describe('CheckComponent', () => {
let component: CheckComponent; let component: CheckComponent
let fixture: ComponentFixture<CheckComponent>; let fixture: ComponentFixture<CheckComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ CheckComponent ] declarations: [CheckComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CheckComponent); fixture = TestBed.createComponent(CheckComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,22 +1,22 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid'
import { AbstractInputComponent } from '../abstract-input'; import { AbstractInputComponent } from '../abstract-input'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => CheckComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => CheckComponent),
}], multi: true,
},
],
selector: 'app-input-check', selector: 'app-input-check',
templateUrl: './check.component.html', templateUrl: './check.component.html',
styleUrls: ['./check.component.scss'] styleUrls: ['./check.component.scss'],
}) })
export class CheckComponent extends AbstractInputComponent<boolean> { export class CheckComponent extends AbstractInputComponent<boolean> {
constructor() { constructor() {
super() super()
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ColorComponent } from './color.component'; import { ColorComponent } from './color.component'
describe('ColorComponent', () => { describe('ColorComponent', () => {
let component: ColorComponent; let component: ColorComponent
let fixture: ComponentFixture<ColorComponent>; let fixture: ComponentFixture<ColorComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ColorComponent ] declarations: [ColorComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ColorComponent); fixture = TestBed.createComponent(ColorComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,20 +1,21 @@
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { randomColor } from 'src/app/utils/color'; import { randomColor } from 'src/app/utils/color'
import { AbstractInputComponent } from '../abstract-input'; import { AbstractInputComponent } from '../abstract-input'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => ColorComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => ColorComponent),
}], multi: true,
},
],
selector: 'app-input-color', selector: 'app-input-color',
templateUrl: './color.component.html', templateUrl: './color.component.html',
styleUrls: ['./color.component.scss'] styleUrls: ['./color.component.scss'],
}) })
export class ColorComponent extends AbstractInputComponent<string> { export class ColorComponent extends AbstractInputComponent<string> {
constructor() { constructor() {
super() super()
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DateComponent } from './date.component'; import { DateComponent } from './date.component'
describe('DateComponent', () => { describe('DateComponent', () => {
let component: DateComponent; let component: DateComponent
let fixture: ComponentFixture<DateComponent>; let fixture: ComponentFixture<DateComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DateComponent ] declarations: [DateComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DateComponent); fixture = TestBed.createComponent(DateComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,21 +1,24 @@
import { Component, forwardRef, OnInit } from '@angular/core'; import { Component, forwardRef, OnInit } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { SettingsService } from 'src/app/services/settings.service'; import { SettingsService } from 'src/app/services/settings.service'
import { AbstractInputComponent } from '../abstract-input'; import { AbstractInputComponent } from '../abstract-input'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => DateComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => DateComponent),
}], multi: true,
},
],
selector: 'app-input-date', selector: 'app-input-date',
templateUrl: './date.component.html', templateUrl: './date.component.html',
styleUrls: ['./date.component.scss'] styleUrls: ['./date.component.scss'],
}) })
export class DateComponent extends AbstractInputComponent<string> implements OnInit { export class DateComponent
extends AbstractInputComponent<string>
implements OnInit
{
constructor(private settings: SettingsService) { constructor(private settings: SettingsService) {
super() super()
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NumberComponent } from './number.component'; import { NumberComponent } from './number.component'
describe('NumberComponent', () => { describe('NumberComponent', () => {
let component: NumberComponent; let component: NumberComponent
let fixture: ComponentFixture<NumberComponent>; let fixture: ComponentFixture<NumberComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ NumberComponent ] declarations: [NumberComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(NumberComponent); fixture = TestBed.createComponent(NumberComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,21 +1,22 @@
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type'; import { FILTER_ASN_ISNULL } from 'src/app/data/filter-rule-type'
import { DocumentService } from 'src/app/services/rest/document.service'; import { DocumentService } from 'src/app/services/rest/document.service'
import { AbstractInputComponent } from '../abstract-input'; import { AbstractInputComponent } from '../abstract-input'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => NumberComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => NumberComponent),
}], multi: true,
},
],
selector: 'app-input-number', selector: 'app-input-number',
templateUrl: './number.component.html', templateUrl: './number.component.html',
styleUrls: ['./number.component.scss'] styleUrls: ['./number.component.scss'],
}) })
export class NumberComponent extends AbstractInputComponent<number> { export class NumberComponent extends AbstractInputComponent<number> {
constructor(private documentService: DocumentService) { constructor(private documentService: DocumentService) {
super() super()
} }
@ -24,16 +25,17 @@ export class NumberComponent extends AbstractInputComponent<number> {
if (this.value) { if (this.value) {
return return
} }
this.documentService.listFiltered(1, 1, "archive_serial_number", true, [{rule_type: FILTER_ASN_ISNULL, value: "false"}]).subscribe( this.documentService
results => { .listFiltered(1, 1, 'archive_serial_number', true, [
{ rule_type: FILTER_ASN_ISNULL, value: 'false' },
])
.subscribe((results) => {
if (results.count > 0) { if (results.count > 0) {
this.value = results.results[0].archive_serial_number + 1 this.value = results.results[0].archive_serial_number + 1
} else { } else {
this.value = 1 this.value = 1
} }
this.onChange(this.value) this.onChange(this.value)
} })
)
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SelectComponent } from './select.component'; import { SelectComponent } from './select.component'
describe('SelectComponent', () => { describe('SelectComponent', () => {
let component: SelectComponent; let component: SelectComponent
let fixture: ComponentFixture<SelectComponent>; let fixture: ComponentFixture<SelectComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SelectComponent ] declarations: [SelectComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SelectComponent); fixture = TestBed.createComponent(SelectComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,23 +1,30 @@
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; import {
import { NG_VALUE_ACCESSOR } from '@angular/forms'; Component,
import { AbstractInputComponent } from '../abstract-input'; EventEmitter,
forwardRef,
Input,
Output,
} from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { AbstractInputComponent } from '../abstract-input'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => SelectComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => SelectComponent),
}], multi: true,
},
],
selector: 'app-input-select', selector: 'app-input-select',
templateUrl: './select.component.html', templateUrl: './select.component.html',
styleUrls: ['./select.component.scss'] styleUrls: ['./select.component.scss'],
}) })
export class SelectComponent extends AbstractInputComponent<number> { export class SelectComponent extends AbstractInputComponent<number> {
constructor() { constructor() {
super() super()
this.addItemRef = this.addItem.bind(this) this.addItemRef = this.addItem.bind(this)
} }
@Input() @Input()
items: any[] items: any[]
@ -47,7 +54,9 @@ export class SelectComponent extends AbstractInputComponent<number> {
getSuggestions() { getSuggestions() {
if (this.suggestions && this.items) { if (this.suggestions && this.items) {
return this.suggestions.filter(id => id != this.value).map(id => this.items.find(item => item.id == id)) return this.suggestions
.filter((id) => id != this.value)
.map((id) => this.items.find((item) => item.id == id))
} else { } else {
return [] return []
} }
@ -75,7 +84,6 @@ export class SelectComponent extends AbstractInputComponent<number> {
onBlur() { onBlur() {
setTimeout(() => { setTimeout(() => {
this.clearLastSearchTerm() this.clearLastSearchTerm()
}, 3000); }, 3000)
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TagsComponent } from './tags.component'; import { TagsComponent } from './tags.component'
describe('TagsComponent', () => { describe('TagsComponent', () => {
let component: TagsComponent; let component: TagsComponent
let fixture: ComponentFixture<TagsComponent>; let fixture: ComponentFixture<TagsComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ TagsComponent ] declarations: [TagsComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(TagsComponent); fixture = TestBed.createComponent(TagsComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,45 +1,46 @@
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'; import { TagEditDialogComponent } from 'src/app/components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component'
import { PaperlessTag } from 'src/app/data/paperless-tag'; import { PaperlessTag } from 'src/app/data/paperless-tag'
import { TagService } from 'src/app/services/rest/tag.service'; import { TagService } from 'src/app/services/rest/tag.service'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => TagsComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => TagsComponent),
}], multi: true,
},
],
selector: 'app-input-tags', selector: 'app-input-tags',
templateUrl: './tags.component.html', templateUrl: './tags.component.html',
styleUrls: ['./tags.component.scss'] styleUrls: ['./tags.component.scss'],
}) })
export class TagsComponent implements OnInit, ControlValueAccessor { export class TagsComponent implements OnInit, ControlValueAccessor {
constructor(private tagService: TagService, private modalService: NgbModal) { constructor(private tagService: TagService, private modalService: NgbModal) {
this.createTagRef = this.createTag.bind(this) this.createTagRef = this.createTag.bind(this)
} }
onChange = (newValue: number[]) => {}; onChange = (newValue: number[]) => {}
onTouched = () => {}; onTouched = () => {}
writeValue(newValue: number[]): void { writeValue(newValue: number[]): void {
this.value = newValue this.value = newValue
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.onChange = fn; this.onChange = fn
} }
registerOnTouched(fn: any): void { registerOnTouched(fn: any): void {
this.onTouched = fn; this.onTouched = fn
} }
setDisabledState?(isDisabled: boolean): void { setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled
} }
ngOnInit(): void { ngOnInit(): void {
this.tagService.listAll().subscribe(result => { this.tagService.listAll().subscribe((result) => {
this.tags = result.results this.tags = result.results
}) })
} }
@ -63,7 +64,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
getTag(id) { getTag(id) {
if (this.tags) { if (this.tags) {
return this.tags.find(tag => tag.id == id) return this.tags.find((tag) => tag.id == id)
} else { } else {
return null return null
} }
@ -80,12 +81,15 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
} }
createTag(name: string = null) { createTag(name: string = null) {
var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'}) var modal = this.modalService.open(TagEditDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.dialogMode = 'create' modal.componentInstance.dialogMode = 'create'
if (name) modal.componentInstance.object = { name: name } if (name) modal.componentInstance.object = { name: name }
else if (this._lastSearchTerm) modal.componentInstance.object = { name: this._lastSearchTerm } else if (this._lastSearchTerm)
modal.componentInstance.success.subscribe(newTag => { modal.componentInstance.object = { name: this._lastSearchTerm }
this.tagService.listAll().subscribe(tags => { modal.componentInstance.success.subscribe((newTag) => {
this.tagService.listAll().subscribe((tags) => {
this.tags = tags.results this.tags = tags.results
this.value = [...this.value, newTag.id] this.value = [...this.value, newTag.id]
this.onChange(this.value) this.onChange(this.value)
@ -95,7 +99,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
getSuggestions() { getSuggestions() {
if (this.suggestions && this.tags) { if (this.suggestions && this.tags) {
return this.suggestions.filter(id => !this.value.includes(id)).map(id => this.tags.find(tag => tag.id == id)) return this.suggestions
.filter((id) => !this.value.includes(id))
.map((id) => this.tags.find((tag) => tag.id == id))
} else { } else {
return [] return []
} }
@ -117,7 +123,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
onBlur() { onBlur() {
setTimeout(() => { setTimeout(() => {
this.clearLastSearchTerm() this.clearLastSearchTerm()
}, 3000); }, 3000)
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TextComponent } from './text.component'; import { TextComponent } from './text.component'
describe('TextComponent', () => { describe('TextComponent', () => {
let component: TextComponent; let component: TextComponent
let fixture: ComponentFixture<TextComponent>; let fixture: ComponentFixture<TextComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ TextComponent ] declarations: [TextComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(TextComponent); fixture = TestBed.createComponent(TextComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,21 +1,21 @@
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR } from '@angular/forms'; import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { AbstractInputComponent } from '../abstract-input'; import { AbstractInputComponent } from '../abstract-input'
@Component({ @Component({
providers: [{ providers: [
provide: NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => TextComponent), provide: NG_VALUE_ACCESSOR,
multi: true useExisting: forwardRef(() => TextComponent),
}], multi: true,
},
],
selector: 'app-input-text', selector: 'app-input-text',
templateUrl: './text.component.html', templateUrl: './text.component.html',
styleUrls: ['./text.component.scss'] styleUrls: ['./text.component.scss'],
}) })
export class TextComponent extends AbstractInputComponent<string> { export class TextComponent extends AbstractInputComponent<string> {
constructor() { constructor() {
super() super()
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { PageHeaderComponent } from './page-header.component'; import { PageHeaderComponent } from './page-header.component'
describe('PageHeaderComponent', () => { describe('PageHeaderComponent', () => {
let component: PageHeaderComponent; let component: PageHeaderComponent
let fixture: ComponentFixture<PageHeaderComponent>; let fixture: ComponentFixture<PageHeaderComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PageHeaderComponent ] declarations: [PageHeaderComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(PageHeaderComponent); fixture = TestBed.createComponent(PageHeaderComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,17 +1,16 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core'
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser'
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment'
@Component({ @Component({
selector: 'app-page-header', selector: 'app-page-header',
templateUrl: './page-header.component.html', templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.scss'] styleUrls: ['./page-header.component.scss'],
}) })
export class PageHeaderComponent { export class PageHeaderComponent {
constructor(private titleService: Title) {}
constructor(private titleService: Title) { } _title = ''
_title = ""
@Input() @Input()
set title(title: string) { set title(title: string) {
@ -24,6 +23,5 @@ export class PageHeaderComponent {
} }
@Input() @Input()
subTitle: string = "" subTitle: string = ''
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SelectDialogComponent } from './select-dialog.component'; import { SelectDialogComponent } from './select-dialog.component'
describe('SelectDialogComponent', () => { describe('SelectDialogComponent', () => {
let component: SelectDialogComponent; let component: SelectDialogComponent
let fixture: ComponentFixture<SelectDialogComponent>; let fixture: ComponentFixture<SelectDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SelectDialogComponent ] declarations: [SelectDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SelectDialogComponent); fixture = TestBed.createComponent(SelectDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,14 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ObjectWithId } from 'src/app/data/object-with-id'; import { ObjectWithId } from 'src/app/data/object-with-id'
@Component({ @Component({
selector: 'app-select-dialog', selector: 'app-select-dialog',
templateUrl: './select-dialog.component.html', templateUrl: './select-dialog.component.html',
styleUrls: ['./select-dialog.component.scss'] styleUrls: ['./select-dialog.component.scss'],
}) })
export class SelectDialogComponent implements OnInit { export class SelectDialogComponent implements OnInit {
constructor(public activeModal: NgbActiveModal) { } constructor(public activeModal: NgbActiveModal) {}
@Output() @Output()
public selectClicked = new EventEmitter() public selectClicked = new EventEmitter()
@ -25,8 +24,7 @@ export class SelectDialogComponent implements OnInit {
selected: number selected: number
ngOnInit(): void { ngOnInit(): void {}
}
cancelClicked() { cancelClicked() {
this.activeModal.close() this.activeModal.close()

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TagComponent } from './tag.component'; import { TagComponent } from './tag.component'
describe('TagComponent', () => { describe('TagComponent', () => {
let component: TagComponent; let component: TagComponent
let fixture: ComponentFixture<TagComponent>; let fixture: ComponentFixture<TagComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ TagComponent ] declarations: [TagComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(TagComponent); fixture = TestBed.createComponent(TagComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,25 +1,22 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core'
import { PaperlessTag } from 'src/app/data/paperless-tag'; import { PaperlessTag } from 'src/app/data/paperless-tag'
@Component({ @Component({
selector: 'app-tag', selector: 'app-tag',
templateUrl: './tag.component.html', templateUrl: './tag.component.html',
styleUrls: ['./tag.component.scss'] styleUrls: ['./tag.component.scss'],
}) })
export class TagComponent implements OnInit { export class TagComponent implements OnInit {
constructor() {}
constructor() { }
@Input() @Input()
tag: PaperlessTag tag: PaperlessTag
@Input() @Input()
linkTitle: string = "" linkTitle: string = ''
@Input() @Input()
clickable: boolean = false clickable: boolean = false
ngOnInit(): void { ngOnInit(): void {}
}
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ToastsComponent } from './toasts.component'; import { ToastsComponent } from './toasts.component'
describe('ToastsComponent', () => { describe('ToastsComponent', () => {
let component: ToastsComponent; let component: ToastsComponent
let fixture: ComponentFixture<ToastsComponent>; let fixture: ComponentFixture<ToastsComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ToastsComponent ] declarations: [ToastsComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(ToastsComponent); fixture = TestBed.createComponent(ToastsComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,14 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core'
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs'
import { Toast, ToastService } from 'src/app/services/toast.service'; import { Toast, ToastService } from 'src/app/services/toast.service'
@Component({ @Component({
selector: 'app-toasts', selector: 'app-toasts',
templateUrl: './toasts.component.html', templateUrl: './toasts.component.html',
styleUrls: ['./toasts.component.scss'] styleUrls: ['./toasts.component.scss'],
}) })
export class ToastsComponent implements OnInit, OnDestroy { export class ToastsComponent implements OnInit, OnDestroy {
constructor(private toastService: ToastService) {}
constructor(private toastService: ToastService) { }
subscription: Subscription subscription: Subscription
@ -20,7 +19,8 @@ export class ToastsComponent implements OnInit, OnDestroy {
} }
ngOnInit(): void { ngOnInit(): void {
this.subscription = this.toastService.getToasts().subscribe(toasts => this.toasts = toasts) this.subscription = this.toastService
.getToasts()
.subscribe((toasts) => (this.toasts = toasts))
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DashboardComponent } from './dashboard.component'; import { DashboardComponent } from './dashboard.component'
describe('DashboardComponent', () => { describe('DashboardComponent', () => {
let component: DashboardComponent; let component: DashboardComponent
let fixture: ComponentFixture<DashboardComponent>; let fixture: ComponentFixture<DashboardComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DashboardComponent ] declarations: [DashboardComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent); fixture = TestBed.createComponent(DashboardComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,20 +1,15 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { Meta } from '@angular/platform-browser'; import { Meta } from '@angular/platform-browser'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'; import { SavedViewService } from 'src/app/services/rest/saved-view.service'
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
templateUrl: './dashboard.component.html', templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'] styleUrls: ['./dashboard.component.scss'],
}) })
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {
constructor(private savedViewService: SavedViewService, private meta: Meta) {}
constructor(
private savedViewService: SavedViewService,
private meta: Meta
) { }
get displayName() { get displayName() {
let tagFullName = this.meta.getTag('name=full_name') let tagFullName = this.meta.getTag('name=full_name')
@ -39,9 +34,10 @@ export class DashboardComponent implements OnInit {
savedViews: PaperlessSavedView[] = [] savedViews: PaperlessSavedView[] = []
ngOnInit(): void { ngOnInit(): void {
this.savedViewService.listAll().subscribe(results => { this.savedViewService.listAll().subscribe((results) => {
this.savedViews = results.results.filter(savedView => savedView.show_on_dashboard) this.savedViews = results.results.filter(
(savedView) => savedView.show_on_dashboard
)
}) })
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SavedViewWidgetComponent } from './saved-view-widget.component'; import { SavedViewWidgetComponent } from './saved-view-widget.component'
describe('SavedViewWidgetComponent', () => { describe('SavedViewWidgetComponent', () => {
let component: SavedViewWidgetComponent; let component: SavedViewWidgetComponent
let fixture: ComponentFixture<SavedViewWidgetComponent>; let fixture: ComponentFixture<SavedViewWidgetComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SavedViewWidgetComponent ] declarations: [SavedViewWidgetComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SavedViewWidgetComponent); fixture = TestBed.createComponent(SavedViewWidgetComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,24 +1,24 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, Input, OnDestroy, OnInit } from '@angular/core'
import { Router } from '@angular/router'; import { Router } from '@angular/router'
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs'
import { PaperlessDocument } from 'src/app/data/paperless-document'; import { PaperlessDocument } from 'src/app/data/paperless-document'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'; import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentService } from 'src/app/services/rest/document.service'; import { DocumentService } from 'src/app/services/rest/document.service'
@Component({ @Component({
selector: 'app-saved-view-widget', selector: 'app-saved-view-widget',
templateUrl: './saved-view-widget.component.html', templateUrl: './saved-view-widget.component.html',
styleUrls: ['./saved-view-widget.component.scss'] styleUrls: ['./saved-view-widget.component.scss'],
}) })
export class SavedViewWidgetComponent implements OnInit, OnDestroy { export class SavedViewWidgetComponent implements OnInit, OnDestroy {
constructor( constructor(
private documentService: DocumentService, private documentService: DocumentService,
private router: Router, private router: Router,
private list: DocumentListViewService, private list: DocumentListViewService,
private consumerStatusService: ConsumerStatusService) { } private consumerStatusService: ConsumerStatusService
) {}
@Input() @Input()
savedView: PaperlessSavedView savedView: PaperlessSavedView
@ -29,9 +29,11 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.reload() this.reload()
this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { this.subscription = this.consumerStatusService
this.reload() .onDocumentConsumptionFinished()
}) .subscribe((status) => {
this.reload()
})
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -39,9 +41,17 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
} }
reload() { reload() {
this.documentService.listFiltered(1,10,this.savedView.sort_field, this.savedView.sort_reverse, this.savedView.filter_rules).subscribe(result => { this.documentService
this.documents = result.results .listFiltered(
}) 1,
10,
this.savedView.sort_field,
this.savedView.sort_reverse,
this.savedView.filter_rules
)
.subscribe((result) => {
this.documents = result.results
})
} }
showAll() { showAll() {
@ -49,8 +59,7 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
this.router.navigate(['view', this.savedView.id]) this.router.navigate(['view', this.savedView.id])
} else { } else {
this.list.loadSavedView(this.savedView, true) this.list.loadSavedView(this.savedView, true)
this.router.navigate(["documents"]) this.router.navigate(['documents'])
} }
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { StatisticsWidgetComponent } from './statistics-widget.component'; import { StatisticsWidgetComponent } from './statistics-widget.component'
describe('StatisticsWidgetComponent', () => { describe('StatisticsWidgetComponent', () => {
let component: StatisticsWidgetComponent; let component: StatisticsWidgetComponent
let fixture: ComponentFixture<StatisticsWidgetComponent>; let fixture: ComponentFixture<StatisticsWidgetComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ StatisticsWidgetComponent ] declarations: [StatisticsWidgetComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(StatisticsWidgetComponent); fixture = TestBed.createComponent(StatisticsWidgetComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,24 +1,24 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http'
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core'
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'; import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment'
export interface Statistics { export interface Statistics {
documents_total?: number documents_total?: number
documents_inbox?: number documents_inbox?: number
} }
@Component({ @Component({
selector: 'app-statistics-widget', selector: 'app-statistics-widget',
templateUrl: './statistics-widget.component.html', templateUrl: './statistics-widget.component.html',
styleUrls: ['./statistics-widget.component.scss'] styleUrls: ['./statistics-widget.component.scss'],
}) })
export class StatisticsWidgetComponent implements OnInit, OnDestroy { export class StatisticsWidgetComponent implements OnInit, OnDestroy {
constructor(
constructor(private http: HttpClient, private http: HttpClient,
private consumerStatusService: ConsumerStatusService) { } private consumerStatusService: ConsumerStatusService
) {}
statistics: Statistics = {} statistics: Statistics = {}
@ -29,20 +29,21 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy {
} }
reload() { reload() {
this.getStatistics().subscribe(statistics => { this.getStatistics().subscribe((statistics) => {
this.statistics = statistics this.statistics = statistics
}) })
} }
ngOnInit(): void { ngOnInit(): void {
this.reload() this.reload()
this.subscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { this.subscription = this.consumerStatusService
this.reload() .onDocumentConsumptionFinished()
}) .subscribe((status) => {
this.reload()
})
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscription.unsubscribe() this.subscription.unsubscribe()
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { UploadFileWidgetComponent } from './upload-file-widget.component'; import { UploadFileWidgetComponent } from './upload-file-widget.component'
describe('UploadFileWidgetComponent', () => { describe('UploadFileWidgetComponent', () => {
let component: UploadFileWidgetComponent; let component: UploadFileWidgetComponent
let fixture: ComponentFixture<UploadFileWidgetComponent>; let fixture: ComponentFixture<UploadFileWidgetComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ UploadFileWidgetComponent ] declarations: [UploadFileWidgetComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(UploadFileWidgetComponent); fixture = TestBed.createComponent(UploadFileWidgetComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,19 @@
import { HttpEventType } from '@angular/common/http'; import { HttpEventType } from '@angular/common/http'
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'; import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import { ConsumerStatusService, FileStatus, FileStatusPhase } from 'src/app/services/consumer-status.service'; import {
import { DocumentService } from 'src/app/services/rest/document.service'; ConsumerStatusService,
FileStatus,
FileStatusPhase,
} from 'src/app/services/consumer-status.service'
import { DocumentService } from 'src/app/services/rest/document.service'
const MAX_ALERTS = 5 const MAX_ALERTS = 5
@Component({ @Component({
selector: 'app-upload-file-widget', selector: 'app-upload-file-widget',
templateUrl: './upload-file-widget.component.html', templateUrl: './upload-file-widget.component.html',
styleUrls: ['./upload-file-widget.component.scss'] styleUrls: ['./upload-file-widget.component.scss'],
}) })
export class UploadFileWidgetComponent implements OnInit { export class UploadFileWidgetComponent implements OnInit {
alertsExpanded = false alertsExpanded = false
@ -17,7 +21,7 @@ export class UploadFileWidgetComponent implements OnInit {
constructor( constructor(
private documentService: DocumentService, private documentService: DocumentService,
private consumerStatusService: ConsumerStatusService private consumerStatusService: ConsumerStatusService
) { } ) {}
getStatus() { getStatus() {
return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS) return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
@ -25,7 +29,8 @@ export class UploadFileWidgetComponent implements OnInit {
getStatusSummary() { getStatusSummary() {
let strings = [] let strings = []
let countUploadingAndProcessing = this.consumerStatusService.getConsumerStatusNotCompleted().length let countUploadingAndProcessing =
this.consumerStatusService.getConsumerStatusNotCompleted().length
let countFailed = this.getStatusFailed().length let countFailed = this.getStatusFailed().length
let countSuccess = this.getStatusSuccess().length let countSuccess = this.getStatusSuccess().length
if (countUploadingAndProcessing > 0) { if (countUploadingAndProcessing > 0) {
@ -37,16 +42,21 @@ export class UploadFileWidgetComponent implements OnInit {
if (countSuccess > 0) { if (countSuccess > 0) {
strings.push($localize`Added: ${countSuccess}`) strings.push($localize`Added: ${countSuccess}`)
} }
return strings.join($localize`:this string is used to separate processing, failed and added on the file upload widget:, `) return strings.join(
$localize`:this string is used to separate processing, failed and added on the file upload widget:, `
)
} }
getStatusHidden() { getStatusHidden() {
if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS) return [] if (this.consumerStatusService.getConsumerStatus().length < MAX_ALERTS)
return []
else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS) else return this.consumerStatusService.getConsumerStatus().slice(MAX_ALERTS)
} }
getStatusUploading() { getStatusUploading() {
return this.consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING) return this.consumerStatusService.getConsumerStatus(
FileStatusPhase.UPLOADING
)
} }
getStatusFailed() { getStatusFailed() {
@ -64,7 +74,7 @@ export class UploadFileWidgetComponent implements OnInit {
let current = 0 let current = 0
let max = 0 let max = 0
this.getStatusUploading().forEach(status => { this.getStatusUploading().forEach((status) => {
current += status.currentPhaseProgress current += status.currentPhaseProgress
max += status.currentPhaseMaxProgress max += status.currentPhaseMaxProgress
}) })
@ -73,18 +83,21 @@ export class UploadFileWidgetComponent implements OnInit {
} }
isFinished(status: FileStatus) { isFinished(status: FileStatus) {
return status.phase == FileStatusPhase.FAILED || status.phase == FileStatusPhase.SUCCESS return (
status.phase == FileStatusPhase.FAILED ||
status.phase == FileStatusPhase.SUCCESS
)
} }
getStatusColor(status: FileStatus) { getStatusColor(status: FileStatus) {
switch (status.phase) { switch (status.phase) {
case FileStatusPhase.PROCESSING: case FileStatusPhase.PROCESSING:
case FileStatusPhase.UPLOADING: case FileStatusPhase.UPLOADING:
return "primary" return 'primary'
case FileStatusPhase.FAILED: case FileStatusPhase.FAILED:
return "danger" return 'danger'
case FileStatusPhase.SUCCESS: case FileStatusPhase.SUCCESS:
return "success" return 'success'
} }
} }
@ -96,20 +109,16 @@ export class UploadFileWidgetComponent implements OnInit {
this.consumerStatusService.dismissCompleted() this.consumerStatusService.dismissCompleted()
} }
ngOnInit(): void { ngOnInit(): void {}
}
public fileOver(event){ public fileOver(event) {}
}
public fileLeave(event){ public fileLeave(event) {}
}
public dropped(files: NgxFileDropEntry[]) { public dropped(files: NgxFileDropEntry[]) {
for (const droppedFile of files) { for (const droppedFile of files) {
if (droppedFile.fileEntry.isFile) { if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
fileEntry.file((file: File) => { fileEntry.file((file: File) => {
let formData = new FormData() let formData = new FormData()
formData.append('document', file, file.name) formData.append('document', file, file.name)
@ -117,29 +126,37 @@ export class UploadFileWidgetComponent implements OnInit {
status.message = $localize`Connecting...` status.message = $localize`Connecting...`
this.documentService.uploadDocument(formData).subscribe(event => { this.documentService.uploadDocument(formData).subscribe(
if (event.type == HttpEventType.UploadProgress) { (event) => {
status.updateProgress(FileStatusPhase.UPLOADING, event.loaded, event.total) if (event.type == HttpEventType.UploadProgress) {
status.message = $localize`Uploading...` status.updateProgress(
} else if (event.type == HttpEventType.Response) { FileStatusPhase.UPLOADING,
status.taskId = event.body["task_id"] event.loaded,
status.message = $localize`Upload complete, waiting...` event.total
} )
status.message = $localize`Uploading...`
}, error => { } else if (event.type == HttpEventType.Response) {
switch (error.status) { status.taskId = event.body['task_id']
case 400: { status.message = $localize`Upload complete, waiting...`
this.consumerStatusService.fail(status, error.error.document)
break;
} }
default: { },
this.consumerStatusService.fail(status, $localize`HTTP error: ${error.status} ${error.statusText}`) (error) => {
break; switch (error.status) {
case 400: {
this.consumerStatusService.fail(status, error.error.document)
break
}
default: {
this.consumerStatusService.fail(
status,
$localize`HTTP error: ${error.status} ${error.statusText}`
)
break
}
} }
} }
)
}) })
});
} }
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { WelcomeWidgetComponent } from './welcome-widget.component'; import { WelcomeWidgetComponent } from './welcome-widget.component'
describe('WelcomeWidgetComponent', () => { describe('WelcomeWidgetComponent', () => {
let component: WelcomeWidgetComponent; let component: WelcomeWidgetComponent
let fixture: ComponentFixture<WelcomeWidgetComponent>; let fixture: ComponentFixture<WelcomeWidgetComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ WelcomeWidgetComponent ] declarations: [WelcomeWidgetComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(WelcomeWidgetComponent); fixture = TestBed.createComponent(WelcomeWidgetComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
@Component({ @Component({
selector: 'app-welcome-widget', selector: 'app-welcome-widget',
templateUrl: './welcome-widget.component.html', templateUrl: './welcome-widget.component.html',
styleUrls: ['./welcome-widget.component.scss'] styleUrls: ['./welcome-widget.component.scss'],
}) })
export class WelcomeWidgetComponent implements OnInit { export class WelcomeWidgetComponent implements OnInit {
constructor() {}
constructor() { } ngOnInit(): void {}
ngOnInit(): void {
}
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { WidgetFrameComponent } from './widget-frame.component'; import { WidgetFrameComponent } from './widget-frame.component'
describe('WidgetFrameComponent', () => { describe('WidgetFrameComponent', () => {
let component: WidgetFrameComponent; let component: WidgetFrameComponent
let fixture: ComponentFixture<WidgetFrameComponent>; let fixture: ComponentFixture<WidgetFrameComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ WidgetFrameComponent ] declarations: [WidgetFrameComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(WidgetFrameComponent); fixture = TestBed.createComponent(WidgetFrameComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,18 +1,15 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core'
@Component({ @Component({
selector: 'app-widget-frame', selector: 'app-widget-frame',
templateUrl: './widget-frame.component.html', templateUrl: './widget-frame.component.html',
styleUrls: ['./widget-frame.component.scss'] styleUrls: ['./widget-frame.component.scss'],
}) })
export class WidgetFrameComponent implements OnInit { export class WidgetFrameComponent implements OnInit {
constructor() {}
constructor() { }
@Input() @Input()
title: string title: string
ngOnInit(): void { ngOnInit(): void {}
}
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentAsnComponent } from './document-asn.component'; import { DocumentAsnComponent } from './document-asn.component'
describe('DocumentASNComponentComponent', () => { describe('DocumentASNComponentComponent', () => {
let component: DocumentAsnComponent; let component: DocumentAsnComponent
let fixture: ComponentFixture<DocumentAsnComponent>; let fixture: ComponentFixture<DocumentAsnComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentAsnComponent ] declarations: [DocumentAsnComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentAsnComponent); fixture = TestBed.createComponent(DocumentAsnComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,34 +1,33 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
import {DocumentService} from "../../services/rest/document.service"; import { DocumentService } from '../../services/rest/document.service'
import {ActivatedRoute, Router} from "@angular/router"; import { ActivatedRoute, Router } from '@angular/router'
import {FILTER_ASN} from "../../data/filter-rule-type"; import { FILTER_ASN } from '../../data/filter-rule-type'
@Component({ @Component({
selector: 'app-document-asncomponent', selector: 'app-document-asncomponent',
templateUrl: './document-asn.component.html', templateUrl: './document-asn.component.html',
styleUrls: ['./document-asn.component.scss'] styleUrls: ['./document-asn.component.scss'],
}) })
export class DocumentAsnComponent implements OnInit { export class DocumentAsnComponent implements OnInit {
asn: string asn: string
constructor( constructor(
private documentsService: DocumentService, private documentsService: DocumentService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router) { } private router: Router
) {}
ngOnInit(): void { ngOnInit(): void {
this.route.paramMap.subscribe((paramMap) => {
this.route.paramMap.subscribe(paramMap => { this.asn = paramMap.get('id')
this.asn = paramMap.get('id'); this.documentsService
this.documentsService.listAllFilteredIds([{rule_type: FILTER_ASN, value: this.asn}]).subscribe(documentId => { .listAllFilteredIds([{ rule_type: FILTER_ASN, value: this.asn }])
if (documentId.length == 1) { .subscribe((documentId) => {
this.router.navigate(['documents', documentId[0]]) if (documentId.length == 1) {
} else { this.router.navigate(['documents', documentId[0]])
this.router.navigate(['404']) } else {
} this.router.navigate(['404'])
}) }
})
}) })
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentDetailComponent } from './document-detail.component'; import { DocumentDetailComponent } from './document-detail.component'
describe('DocumentDetailComponent', () => { describe('DocumentDetailComponent', () => {
let component: DocumentDetailComponent; let component: DocumentDetailComponent
let fixture: ComponentFixture<DocumentDetailComponent>; let fixture: ComponentFixture<DocumentDetailComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentDetailComponent ] declarations: [DocumentDetailComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentDetailComponent); fixture = TestBed.createComponent(DocumentDetailComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,38 +1,55 @@
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import {
import { FormControl, FormGroup } from '@angular/forms'; Component,
import { ActivatedRoute, Router } from '@angular/router'; OnInit,
import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap'; OnDestroy,
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; ViewChild,
import { PaperlessDocument } from 'src/app/data/paperless-document'; ElementRef,
import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'; } from '@angular/core'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; import { FormControl, FormGroup } from '@angular/forms'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'; import { ActivatedRoute, Router } from '@angular/router'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { NgbModal, NgbNav } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'; import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'
import { DocumentService } from 'src/app/services/rest/document.service'; import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'; import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'; import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { PDFDocumentProxy } from 'ng2-pdf-viewer'; import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'; import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { TextComponent } from '../common/input/text/text.component'; import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'; import { CorrespondentEditDialogComponent } from '../manage/correspondent-list/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { Observable, Subject, BehaviorSubject } from 'rxjs'; import { DocumentTypeEditDialogComponent } from '../manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component'
import { first, takeUntil, switchMap, map, debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { PDFDocumentProxy } from 'ng2-pdf-viewer'
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'; import { ToastService } from 'src/app/services/toast.service'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'; import { TextComponent } from '../common/input/text/text.component'
import {
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
import { Observable, Subject, BehaviorSubject } from 'rxjs'
import {
first,
takeUntil,
switchMap,
map,
debounceTime,
distinctUntilChanged,
} from 'rxjs/operators'
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
@Component({ @Component({
selector: 'app-document-detail', selector: 'app-document-detail',
templateUrl: './document-detail.component.html', templateUrl: './document-detail.component.html',
styleUrls: ['./document-detail.component.scss'] styleUrls: ['./document-detail.component.scss'],
}) })
export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponent { export class DocumentDetailComponent
implements OnInit, OnDestroy, DirtyComponent
@ViewChild("inputTitle") {
@ViewChild('inputTitle')
titleInput: TextComponent titleInput: TextComponent
expandOriginalMetadata = false expandOriginalMetadata = false
@ -63,7 +80,7 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
correspondent: new FormControl(), correspondent: new FormControl(),
document_type: new FormControl(), document_type: new FormControl(),
archive_serial_number: new FormControl(), archive_serial_number: new FormControl(),
tags: new FormControl([]) tags: new FormControl([]),
}) })
previewCurrentPage: number = 1 previewCurrentPage: number = 1
@ -76,8 +93,13 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
@ViewChild('nav') nav: NgbNav @ViewChild('nav') nav: NgbNav
@ViewChild('pdfPreview') set pdfPreview(element) { @ViewChild('pdfPreview') set pdfPreview(element) {
// this gets called when compontent added or removed from DOM // this gets called when compontent added or removed from DOM
if (element && element.nativeElement.offsetParent !== null && this.nav?.activeId == 4) { // its visible if (
setTimeout(()=> this.nav?.select(1)); element &&
element.nativeElement.offsetParent !== null &&
this.nav?.activeId == 4
) {
// its visible
setTimeout(() => this.nav?.select(1))
} }
} }
@ -92,16 +114,19 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
private documentListViewService: DocumentListViewService, private documentListViewService: DocumentListViewService,
private documentTitlePipe: DocumentTitlePipe, private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService, private toastService: ToastService,
private settings: SettingsService) { private settings: SettingsService
this.titleSubject.pipe( ) {
this.titleSubject
.pipe(
debounceTime(1000), debounceTime(1000),
distinctUntilChanged(), distinctUntilChanged(),
takeUntil(this.unsubscribeNotifier) takeUntil(this.unsubscribeNotifier)
).subscribe(titleValue => { )
.subscribe((titleValue) => {
this.title = titleValue this.title = titleValue
this.documentForm.patchValue({'title': titleValue}) this.documentForm.patchValue({ title: titleValue })
}) })
} }
titleKeyUp(event) { titleKeyUp(event) {
this.titleSubject.next(event.target?.value) this.titleSubject.next(event.target?.value)
@ -112,180 +137,291 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
} }
getContentType() { getContentType() {
return this.metadata?.has_archive_version ? 'application/pdf' : this.metadata?.original_mime_type return this.metadata?.has_archive_version
? 'application/pdf'
: this.metadata?.original_mime_type
} }
ngOnInit(): void { ngOnInit(): void {
this.documentForm.valueChanges.pipe(takeUntil(this.unsubscribeNotifier)).subscribe(wow => { this.documentForm.valueChanges
Object.assign(this.document, this.documentForm.value) .pipe(takeUntil(this.unsubscribeNotifier))
}) .subscribe((wow) => {
Object.assign(this.document, this.documentForm.value)
this.correspondentService.listAll().pipe(first()).subscribe(result => this.correspondents = result.results)
this.documentTypeService.listAll().pipe(first()).subscribe(result => this.documentTypes = result.results)
this.route.paramMap.pipe(switchMap(paramMap => {
const documentId = +paramMap.get('id')
return this.documentsService.get(documentId)
})).pipe(switchMap((doc) => {
this.documentId = doc.id
this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
this.downloadUrl = this.documentsService.getDownloadUrl(this.documentId)
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(this.documentId, true)
this.suggestions = null
if (this.openDocumentService.getOpenDocument(this.documentId)) {
this.updateComponent(this.openDocumentService.getOpenDocument(this.documentId))
} else {
this.openDocumentService.openDocument(doc)
this.updateComponent(doc)
}
// Initialize dirtyCheck
this.store = new BehaviorSubject({
title: doc.title,
content: doc.content,
created: doc.created,
correspondent: doc.correspondent,
document_type: doc.document_type,
archive_serial_number: doc.archive_serial_number,
tags: [...doc.tags]
}) })
this.isDirty$ = dirtyCheck(this.documentForm, this.store.asObservable()) this.correspondentService
.listAll()
.pipe(first())
.subscribe((result) => (this.correspondents = result.results))
this.documentTypeService
.listAll()
.pipe(first())
.subscribe((result) => (this.documentTypes = result.results))
return this.isDirty$.pipe(map(dirty => ({doc, dirty}))) this.route.paramMap
})) .pipe(
.pipe(takeUntil(this.unsubscribeNotifier)) switchMap((paramMap) => {
.subscribe(({doc, dirty}) => { const documentId = +paramMap.get('id')
this.openDocumentService.setDirty(doc.id, dirty) return this.documentsService.get(documentId)
}, error => {this.router.navigate(['404'])}) })
)
.pipe(
switchMap((doc) => {
this.documentId = doc.id
this.previewUrl = this.documentsService.getPreviewUrl(this.documentId)
this.downloadUrl = this.documentsService.getDownloadUrl(
this.documentId
)
this.downloadOriginalUrl = this.documentsService.getDownloadUrl(
this.documentId,
true
)
this.suggestions = null
if (this.openDocumentService.getOpenDocument(this.documentId)) {
this.updateComponent(
this.openDocumentService.getOpenDocument(this.documentId)
)
} else {
this.openDocumentService.openDocument(doc)
this.updateComponent(doc)
}
// Initialize dirtyCheck
this.store = new BehaviorSubject({
title: doc.title,
content: doc.content,
created: doc.created,
correspondent: doc.correspondent,
document_type: doc.document_type,
archive_serial_number: doc.archive_serial_number,
tags: [...doc.tags],
})
this.isDirty$ = dirtyCheck(
this.documentForm,
this.store.asObservable()
)
return this.isDirty$.pipe(map((dirty) => ({ doc, dirty })))
})
)
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(
({ doc, dirty }) => {
this.openDocumentService.setDirty(doc.id, dirty)
},
(error) => {
this.router.navigate(['404'])
}
)
} }
ngOnDestroy() : void { ngOnDestroy(): void {
this.unsubscribeNotifier.next(); this.unsubscribeNotifier.next()
this.unsubscribeNotifier.complete(); this.unsubscribeNotifier.complete()
} }
updateComponent(doc: PaperlessDocument) { updateComponent(doc: PaperlessDocument) {
this.document = doc this.document = doc
this.documentsService.getMetadata(doc.id).pipe(first()).subscribe(result => { this.documentsService
this.metadata = result .getMetadata(doc.id)
}, error => { .pipe(first())
this.metadata = null .subscribe(
}) (result) => {
this.documentsService.getSuggestions(doc.id).pipe(first()).subscribe(result => { this.metadata = result
this.suggestions = result },
}, error => { (error) => {
this.suggestions = null this.metadata = null
}) }
)
this.documentsService
.getSuggestions(doc.id)
.pipe(first())
.subscribe(
(result) => {
this.suggestions = result
},
(error) => {
this.suggestions = null
}
)
this.title = this.documentTitlePipe.transform(doc.title) this.title = this.documentTitlePipe.transform(doc.title)
this.documentForm.patchValue(doc) this.documentForm.patchValue(doc)
} }
createDocumentType(newName: string) { createDocumentType(newName: string) {
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'}) var modal = this.modalService.open(DocumentTypeEditDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.dialogMode = 'create' modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName } if (newName) modal.componentInstance.object = { name: newName }
modal.componentInstance.success.pipe(switchMap(newDocumentType => { modal.componentInstance.success
return this.documentTypeService.listAll().pipe(map(documentTypes => ({newDocumentType, documentTypes}))) .pipe(
})) switchMap((newDocumentType) => {
.pipe(takeUntil(this.unsubscribeNotifier)) return this.documentTypeService
.subscribe(({newDocumentType, documentTypes}) => { .listAll()
this.documentTypes = documentTypes.results .pipe(map((documentTypes) => ({ newDocumentType, documentTypes })))
this.documentForm.get('document_type').setValue(newDocumentType.id) })
}) )
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newDocumentType, documentTypes }) => {
this.documentTypes = documentTypes.results
this.documentForm.get('document_type').setValue(newDocumentType.id)
})
} }
createCorrespondent(newName: string) { createCorrespondent(newName: string) {
var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'}) var modal = this.modalService.open(CorrespondentEditDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.dialogMode = 'create' modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName } if (newName) modal.componentInstance.object = { name: newName }
modal.componentInstance.success.pipe(switchMap(newCorrespondent => { modal.componentInstance.success
return this.correspondentService.listAll().pipe(map(correspondents => ({newCorrespondent, correspondents}))) .pipe(
})) switchMap((newCorrespondent) => {
.pipe(takeUntil(this.unsubscribeNotifier)) return this.correspondentService
.subscribe(({newCorrespondent, correspondents}) => { .listAll()
this.correspondents = correspondents.results .pipe(
this.documentForm.get('correspondent').setValue(newCorrespondent.id) map((correspondents) => ({ newCorrespondent, correspondents }))
}) )
})
)
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newCorrespondent, correspondents }) => {
this.correspondents = correspondents.results
this.documentForm.get('correspondent').setValue(newCorrespondent.id)
})
} }
discard() { discard() {
this.documentsService.get(this.documentId).pipe(first()).subscribe(doc => { this.documentsService
Object.assign(this.document, doc) .get(this.documentId)
this.title = doc.title .pipe(first())
this.documentForm.patchValue(doc) .subscribe(
}, error => {this.router.navigate(['404'])}) (doc) => {
Object.assign(this.document, doc)
this.title = doc.title
this.documentForm.patchValue(doc)
},
(error) => {
this.router.navigate(['404'])
}
)
} }
save() { save() {
this.networkActive = true this.networkActive = true
this.store.next(this.documentForm.value) this.store.next(this.documentForm.value)
this.documentsService.update(this.document).pipe(first()).subscribe(result => { this.documentsService
this.close() .update(this.document)
this.networkActive = false .pipe(first())
this.error = null .subscribe(
}, error => { (result) => {
this.networkActive = false this.close()
this.error = error.error this.networkActive = false
}) this.error = null
},
(error) => {
this.networkActive = false
this.error = error.error
}
)
} }
saveEditNext() { saveEditNext() {
this.networkActive = true this.networkActive = true
this.store.next(this.documentForm.value) this.store.next(this.documentForm.value)
this.documentsService.update(this.document).pipe(switchMap(updateResult => { this.documentsService
return this.documentListViewService.getNext(this.documentId).pipe(map(nextDocId => ({nextDocId, updateResult}))) .update(this.document)
})).pipe(switchMap(({nextDocId, updateResult}) => { .pipe(
if (nextDocId && updateResult) return this.openDocumentService.closeDocument(this.document).pipe(map(closeResult => ({updateResult, nextDocId, closeResult}))) switchMap((updateResult) => {
})) return this.documentListViewService
.pipe(first()) .getNext(this.documentId)
.subscribe(({updateResult, nextDocId, closeResult}) => { .pipe(map((nextDocId) => ({ nextDocId, updateResult })))
this.error = null })
this.networkActive = false )
if (closeResult && updateResult && nextDocId) { .pipe(
this.router.navigate(['documents', nextDocId]) switchMap(({ nextDocId, updateResult }) => {
this.titleInput?.focus() if (nextDocId && updateResult)
} return this.openDocumentService
}, error => { .closeDocument(this.document)
this.networkActive = false .pipe(
this.error = error.error map((closeResult) => ({ updateResult, nextDocId, closeResult }))
}) )
})
)
.pipe(first())
.subscribe(
({ updateResult, nextDocId, closeResult }) => {
this.error = null
this.networkActive = false
if (closeResult && updateResult && nextDocId) {
this.router.navigate(['documents', nextDocId])
this.titleInput?.focus()
}
},
(error) => {
this.networkActive = false
this.error = error.error
}
)
} }
close() { close() {
this.openDocumentService.closeDocument(this.document).pipe(first()).subscribe(closed => { this.openDocumentService
if (!closed) return; .closeDocument(this.document)
if (this.documentListViewService.activeSavedViewId) { .pipe(first())
this.router.navigate(['view', this.documentListViewService.activeSavedViewId]) .subscribe((closed) => {
} else { if (!closed) return
this.router.navigate(['documents']) if (this.documentListViewService.activeSavedViewId) {
} this.router.navigate([
}) 'view',
this.documentListViewService.activeSavedViewId,
])
} else {
this.router.navigate(['documents'])
}
})
} }
delete() { delete() {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.title = $localize`Confirm delete` modal.componentInstance.title = $localize`Confirm delete`
modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?` modal.componentInstance.messageBold = $localize`Do you really want to delete document "${this.document.title}"?`
modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.` modal.componentInstance.message = $localize`The files for this document will be deleted permanently. This operation cannot be undone.`
modal.componentInstance.btnClass = "btn-danger" modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Delete document` modal.componentInstance.btnCaption = $localize`Delete document`
modal.componentInstance.confirmClicked.pipe(switchMap(() => { modal.componentInstance.confirmClicked
modal.componentInstance.buttonsEnabled = false .pipe(
return this.documentsService.delete(this.document) switchMap(() => {
})) modal.componentInstance.buttonsEnabled = false
.pipe(takeUntil(this.unsubscribeNotifier)) return this.documentsService.delete(this.document)
.subscribe(() => { })
modal.close() )
this.close() .pipe(takeUntil(this.unsubscribeNotifier))
}, error => { .subscribe(
this.toastService.showError($localize`Error deleting document: ${JSON.stringify(error)}`) () => {
modal.componentInstance.buttonsEnabled = true modal.close()
}) this.close()
},
(error) => {
this.toastService.showError(
$localize`Error deleting document: ${JSON.stringify(error)}`
)
modal.componentInstance.buttonsEnabled = true
}
)
} }
moreLike() { moreLike() {
this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}]) this.documentListViewService.quickFilter([
{
rule_type: FILTER_FULLTEXT_MORELIKE,
value: this.documentId.toString(),
},
])
} }
hasNext() { hasNext() {
@ -297,19 +433,22 @@ export class DocumentDetailComponent implements OnInit, OnDestroy, DirtyComponen
} }
nextDoc() { nextDoc() {
this.documentListViewService.getNext(this.document.id).subscribe((nextDocId: number) => { this.documentListViewService
this.router.navigate(['documents', nextDocId]) .getNext(this.document.id)
}) .subscribe((nextDocId: number) => {
this.router.navigate(['documents', nextDocId])
})
} }
previousDoc () { previousDoc() {
this.documentListViewService.getPrevious(this.document.id).subscribe((prevDocId: number) => { this.documentListViewService
this.router.navigate(['documents', prevDocId]) .getPrevious(this.document.id)
}) .subscribe((prevDocId: number) => {
this.router.navigate(['documents', prevDocId])
})
} }
pdfPreviewLoaded(pdf: PDFDocumentProxy) { pdfPreviewLoaded(pdf: PDFDocumentProxy) {
this.previewNumPages = pdf.numPages this.previewNumPages = pdf.numPages
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { MetadataCollapseComponent } from './metadata-collapse.component'; import { MetadataCollapseComponent } from './metadata-collapse.component'
describe('MetadataCollapseComponent', () => { describe('MetadataCollapseComponent', () => {
let component: MetadataCollapseComponent; let component: MetadataCollapseComponent
let fixture: ComponentFixture<MetadataCollapseComponent>; let fixture: ComponentFixture<MetadataCollapseComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ MetadataCollapseComponent ] declarations: [MetadataCollapseComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(MetadataCollapseComponent); fixture = TestBed.createComponent(MetadataCollapseComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,13 +1,12 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core'
@Component({ @Component({
selector: 'app-metadata-collapse', selector: 'app-metadata-collapse',
templateUrl: './metadata-collapse.component.html', templateUrl: './metadata-collapse.component.html',
styleUrls: ['./metadata-collapse.component.scss'] styleUrls: ['./metadata-collapse.component.scss'],
}) })
export class MetadataCollapseComponent implements OnInit { export class MetadataCollapseComponent implements OnInit {
constructor() {}
constructor() { }
expand = false expand = false
@ -17,7 +16,5 @@ export class MetadataCollapseComponent implements OnInit {
@Input() @Input()
title = $localize`Metadata` title = $localize`Metadata`
ngOnInit(): void { ngOnInit(): void {}
}
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { BulkEditorComponent } from './bulk-editor.component'; import { BulkEditorComponent } from './bulk-editor.component'
describe('BulkEditorComponent', () => { describe('BulkEditorComponent', () => {
let component: BulkEditorComponent; let component: BulkEditorComponent
let fixture: ComponentFixture<BulkEditorComponent>; let fixture: ComponentFixture<BulkEditorComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ BulkEditorComponent ] declarations: [BulkEditorComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(BulkEditorComponent); fixture = TestBed.createComponent(BulkEditorComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,29 +1,37 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { PaperlessTag } from 'src/app/data/paperless-tag'; import { PaperlessTag } from 'src/app/data/paperless-tag'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { TagService } from 'src/app/services/rest/tag.service'; import { TagService } from 'src/app/services/rest/tag.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { DocumentService, SelectionDataItem } from 'src/app/services/rest/document.service'; import {
import { OpenDocumentsService } from 'src/app/services/open-documents.service'; DocumentService,
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'; SelectionDataItem,
import { ChangedItems, FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; } from 'src/app/services/rest/document.service'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { MatchingModel } from 'src/app/data/matching-model'; import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; import {
import { ToastService } from 'src/app/services/toast.service'; ChangedItems,
import { saveAs } from 'file-saver'; FilterableDropdownSelectionModel,
} from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { MatchingModel } from 'src/app/data/matching-model'
import {
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { saveAs } from 'file-saver'
@Component({ @Component({
selector: 'app-bulk-editor', selector: 'app-bulk-editor',
templateUrl: './bulk-editor.component.html', templateUrl: './bulk-editor.component.html',
styleUrls: ['./bulk-editor.component.scss'] styleUrls: ['./bulk-editor.component.scss'],
}) })
export class BulkEditorComponent { export class BulkEditorComponent {
tags: PaperlessTag[] tags: PaperlessTag[]
correspondents: PaperlessCorrespondent[] correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[] documentTypes: PaperlessDocumentType[]
@ -42,43 +50,63 @@ export class BulkEditorComponent {
private openDocumentService: OpenDocumentsService, private openDocumentService: OpenDocumentsService,
private settings: SettingsService, private settings: SettingsService,
private toastService: ToastService private toastService: ToastService
) { } ) {}
applyOnClose: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE) applyOnClose: boolean = this.settings.get(
showConfirmationDialogs: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS) SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE
)
showConfirmationDialogs: boolean = this.settings.get(
SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS
)
ngOnInit() { ngOnInit() {
this.tagService.listAll().subscribe(result => this.tags = result.results) this.tagService
this.correspondentService.listAll().subscribe(result => this.correspondents = result.results) .listAll()
this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) .subscribe((result) => (this.tags = result.results))
this.correspondentService
.listAll()
.subscribe((result) => (this.correspondents = result.results))
this.documentTypeService
.listAll()
.subscribe((result) => (this.documentTypes = result.results))
} }
private executeBulkOperation(modal, method: string, args) { private executeBulkOperation(modal, method: string, args) {
if (modal) { if (modal) {
modal.componentInstance.buttonsEnabled = false modal.componentInstance.buttonsEnabled = false
} }
this.documentService.bulkEdit(Array.from(this.list.selected), method, args).subscribe( this.documentService
response => { .bulkEdit(Array.from(this.list.selected), method, args)
this.list.reload() .subscribe(
this.list.reduceSelectionToFilter() (response) => {
this.list.selected.forEach(id => { this.list.reload()
this.openDocumentService.refreshDocument(id) this.list.reduceSelectionToFilter()
}) this.list.selected.forEach((id) => {
if (modal) { this.openDocumentService.refreshDocument(id)
modal.close() })
if (modal) {
modal.close()
}
},
(error) => {
if (modal) {
modal.componentInstance.buttonsEnabled = true
}
this.toastService.showError(
$localize`Error executing bulk operation: ${JSON.stringify(
error.error
)}`
)
} }
}, error => { )
if (modal) {
modal.componentInstance.buttonsEnabled = true
}
this.toastService.showError($localize`Error executing bulk operation: ${JSON.stringify(error.error)}`)
}
)
} }
private applySelectionData(items: SelectionDataItem[], selectionModel: FilterableDropdownSelectionModel) { private applySelectionData(
items: SelectionDataItem[],
selectionModel: FilterableDropdownSelectionModel
) {
let selectionData = new Map<number, ToggleableItemState>() let selectionData = new Map<number, ToggleableItemState>()
items.forEach(i => { items.forEach((i) => {
if (i.document_count == this.list.selected.size) { if (i.document_count == this.list.selected.size) {
selectionData.set(i.id, ToggleableItemState.Selected) selectionData.set(i.id, ToggleableItemState.Selected)
} else if (i.document_count > 0) { } else if (i.document_count > 0) {
@ -89,129 +117,210 @@ export class BulkEditorComponent {
} }
openTagsDropdown() { openTagsDropdown() {
this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => { this.documentService
this.applySelectionData(s.selected_tags, this.tagSelectionModel) .getSelectionData(Array.from(this.list.selected))
}) .subscribe((s) => {
this.applySelectionData(s.selected_tags, this.tagSelectionModel)
})
} }
openDocumentTypeDropdown() { openDocumentTypeDropdown() {
this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => { this.documentService
this.applySelectionData(s.selected_document_types, this.documentTypeSelectionModel) .getSelectionData(Array.from(this.list.selected))
}) .subscribe((s) => {
this.applySelectionData(
s.selected_document_types,
this.documentTypeSelectionModel
)
})
} }
openCorrespondentDropdown() { openCorrespondentDropdown() {
this.documentService.getSelectionData(Array.from(this.list.selected)).subscribe(s => { this.documentService
this.applySelectionData(s.selected_correspondents, this.correspondentSelectionModel) .getSelectionData(Array.from(this.list.selected))
}) .subscribe((s) => {
this.applySelectionData(
s.selected_correspondents,
this.correspondentSelectionModel
)
})
} }
private _localizeList(items: MatchingModel[]) { private _localizeList(items: MatchingModel[]) {
if (items.length == 0) { if (items.length == 0) {
return "" return ''
} else if (items.length == 1) { } else if (items.length == 1) {
return $localize`"${items[0].name}"` return $localize`"${items[0].name}"`
} else if (items.length == 2) { } else if (items.length == 2) {
return $localize`:This is for messages like 'modify "tag1" and "tag2"':"${items[0].name}" and "${items[1].name}"` return $localize`:This is for messages like 'modify "tag1" and "tag2"':"${items[0].name}" and "${items[1].name}"`
} else { } else {
let list = items.slice(0, items.length - 1).map(i => $localize`"${i.name}"`).join($localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, `) let list = items
return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${items[items.length - 1].name}"` .slice(0, items.length - 1)
.map((i) => $localize`"${i.name}"`)
.join(
$localize`:this is used to separate enumerations and should probably be a comma and a whitespace in most languages:, `
)
return $localize`:this is for messages like 'modify "tag1", "tag2" and "tag3"':${list} and "${
items[items.length - 1].name
}"`
} }
} }
setTags(changedTags: ChangedItems) { setTags(changedTags: ChangedItems) {
if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 0) return if (
changedTags.itemsToAdd.length == 0 &&
changedTags.itemsToRemove.length == 0
)
return
if (this.showConfirmationDialogs) { if (this.showConfirmationDialogs) {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.title = $localize`Confirm tags assignment` modal.componentInstance.title = $localize`Confirm tags assignment`
if (changedTags.itemsToAdd.length == 1 && changedTags.itemsToRemove.length == 0) { if (
changedTags.itemsToAdd.length == 1 &&
changedTags.itemsToRemove.length == 0
) {
let tag = changedTags.itemsToAdd[0] let tag = changedTags.itemsToAdd[0]
modal.componentInstance.message = $localize`This operation will add the tag "${tag.name}" to ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will add the tag "${tag.name}" to ${this.list.selected.size} selected document(s).`
} else if (changedTags.itemsToAdd.length > 1 && changedTags.itemsToRemove.length == 0) { } else if (
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} to ${this.list.selected.size} selected document(s).` changedTags.itemsToAdd.length > 1 &&
} else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length == 1) { changedTags.itemsToRemove.length == 0
) {
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(
changedTags.itemsToAdd
)} to ${this.list.selected.size} selected document(s).`
} else if (
changedTags.itemsToAdd.length == 0 &&
changedTags.itemsToRemove.length == 1
) {
let tag = changedTags.itemsToRemove[0] let tag = changedTags.itemsToRemove[0]
modal.componentInstance.message = $localize`This operation will remove the tag "${tag.name}" from ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will remove the tag "${tag.name}" from ${this.list.selected.size} selected document(s).`
} else if (changedTags.itemsToAdd.length == 0 && changedTags.itemsToRemove.length > 1) { } else if (
modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(changedTags.itemsToRemove)} from ${this.list.selected.size} selected document(s).` changedTags.itemsToAdd.length == 0 &&
changedTags.itemsToRemove.length > 1
) {
modal.componentInstance.message = $localize`This operation will remove the tags ${this._localizeList(
changedTags.itemsToRemove
)} from ${this.list.selected.size} selected document(s).`
} else { } else {
modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(changedTags.itemsToAdd)} and remove the tags ${this._localizeList(changedTags.itemsToRemove)} on ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will add the tags ${this._localizeList(
changedTags.itemsToAdd
)} and remove the tags ${this._localizeList(
changedTags.itemsToRemove
)} on ${this.list.selected.size} selected document(s).`
} }
modal.componentInstance.btnClass = "btn-warning" modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm` modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => { modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'modify_tags', {"add_tags": changedTags.itemsToAdd.map(t => t.id), "remove_tags": changedTags.itemsToRemove.map(t => t.id)}) this.executeBulkOperation(modal, 'modify_tags', {
add_tags: changedTags.itemsToAdd.map((t) => t.id),
remove_tags: changedTags.itemsToRemove.map((t) => t.id),
})
}) })
} else { } else {
this.executeBulkOperation(null, 'modify_tags', {"add_tags": changedTags.itemsToAdd.map(t => t.id), "remove_tags": changedTags.itemsToRemove.map(t => t.id)}) this.executeBulkOperation(null, 'modify_tags', {
add_tags: changedTags.itemsToAdd.map((t) => t.id),
remove_tags: changedTags.itemsToRemove.map((t) => t.id),
})
} }
} }
setCorrespondents(changedCorrespondents: ChangedItems) { setCorrespondents(changedCorrespondents: ChangedItems) {
if (changedCorrespondents.itemsToAdd.length == 0 && changedCorrespondents.itemsToRemove.length == 0) return if (
changedCorrespondents.itemsToAdd.length == 0 &&
changedCorrespondents.itemsToRemove.length == 0
)
return
let correspondent = changedCorrespondents.itemsToAdd.length > 0 ? changedCorrespondents.itemsToAdd[0] : null let correspondent =
changedCorrespondents.itemsToAdd.length > 0
? changedCorrespondents.itemsToAdd[0]
: null
if (this.showConfirmationDialogs) { if (this.showConfirmationDialogs) {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.title = $localize`Confirm correspondent assignment` modal.componentInstance.title = $localize`Confirm correspondent assignment`
if (correspondent) { if (correspondent) {
modal.componentInstance.message = $localize`This operation will assign the correspondent "${correspondent.name}" to ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will assign the correspondent "${correspondent.name}" to ${this.list.selected.size} selected document(s).`
} else { } else {
modal.componentInstance.message = $localize`This operation will remove the correspondent from ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will remove the correspondent from ${this.list.selected.size} selected document(s).`
} }
modal.componentInstance.btnClass = "btn-warning" modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm` modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => { modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'set_correspondent', {"correspondent": correspondent ? correspondent.id : null}) this.executeBulkOperation(modal, 'set_correspondent', {
correspondent: correspondent ? correspondent.id : null,
})
}) })
} else { } else {
this.executeBulkOperation(null, 'set_correspondent', {"correspondent": correspondent ? correspondent.id : null}) this.executeBulkOperation(null, 'set_correspondent', {
correspondent: correspondent ? correspondent.id : null,
})
} }
} }
setDocumentTypes(changedDocumentTypes: ChangedItems) { setDocumentTypes(changedDocumentTypes: ChangedItems) {
if (changedDocumentTypes.itemsToAdd.length == 0 && changedDocumentTypes.itemsToRemove.length == 0) return if (
changedDocumentTypes.itemsToAdd.length == 0 &&
changedDocumentTypes.itemsToRemove.length == 0
)
return
let documentType = changedDocumentTypes.itemsToAdd.length > 0 ? changedDocumentTypes.itemsToAdd[0] : null let documentType =
changedDocumentTypes.itemsToAdd.length > 0
? changedDocumentTypes.itemsToAdd[0]
: null
if (this.showConfirmationDialogs) { if (this.showConfirmationDialogs) {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.title = $localize`Confirm document type assignment` modal.componentInstance.title = $localize`Confirm document type assignment`
if (documentType) { if (documentType) {
modal.componentInstance.message = $localize`This operation will assign the document type "${documentType.name}" to ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will assign the document type "${documentType.name}" to ${this.list.selected.size} selected document(s).`
} else { } else {
modal.componentInstance.message = $localize`This operation will remove the document type from ${this.list.selected.size} selected document(s).` modal.componentInstance.message = $localize`This operation will remove the document type from ${this.list.selected.size} selected document(s).`
} }
modal.componentInstance.btnClass = "btn-warning" modal.componentInstance.btnClass = 'btn-warning'
modal.componentInstance.btnCaption = $localize`Confirm` modal.componentInstance.btnCaption = $localize`Confirm`
modal.componentInstance.confirmClicked.subscribe(() => { modal.componentInstance.confirmClicked.subscribe(() => {
this.executeBulkOperation(modal, 'set_document_type', {"document_type": documentType ? documentType.id : null}) this.executeBulkOperation(modal, 'set_document_type', {
document_type: documentType ? documentType.id : null,
})
}) })
} else { } else {
this.executeBulkOperation(null, 'set_document_type', {"document_type": documentType ? documentType.id : null}) this.executeBulkOperation(null, 'set_document_type', {
document_type: documentType ? documentType.id : null,
})
} }
} }
applyDelete() { applyDelete() {
let modal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.delayConfirm(5) modal.componentInstance.delayConfirm(5)
modal.componentInstance.title = $localize`Delete confirm` modal.componentInstance.title = $localize`Delete confirm`
modal.componentInstance.messageBold = $localize`This operation will permanently delete ${this.list.selected.size} selected document(s).` modal.componentInstance.messageBold = $localize`This operation will permanently delete ${this.list.selected.size} selected document(s).`
modal.componentInstance.message = $localize`This operation cannot be undone.` modal.componentInstance.message = $localize`This operation cannot be undone.`
modal.componentInstance.btnClass = "btn-danger" modal.componentInstance.btnClass = 'btn-danger'
modal.componentInstance.btnCaption = $localize`Delete document(s)` modal.componentInstance.btnCaption = $localize`Delete document(s)`
modal.componentInstance.confirmClicked.subscribe(() => { modal.componentInstance.confirmClicked.subscribe(() => {
modal.componentInstance.buttonsEnabled = false modal.componentInstance.buttonsEnabled = false
this.executeBulkOperation(modal, "delete", {}) this.executeBulkOperation(modal, 'delete', {})
}) })
} }
downloadSelected(content = "archive") { downloadSelected(content = 'archive') {
this.documentService.bulkDownload(Array.from(this.list.selected), content).subscribe((result: any) => { this.documentService
saveAs(result, 'documents.zip'); .bulkDownload(Array.from(this.list.selected), content)
}) .subscribe((result: any) => {
saveAs(result, 'documents.zip')
})
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentCardLargeComponent } from './document-card-large.component'; import { DocumentCardLargeComponent } from './document-card-large.component'
describe('DocumentCardLargeComponent', () => { describe('DocumentCardLargeComponent', () => {
let component: DocumentCardLargeComponent; let component: DocumentCardLargeComponent
let fixture: ComponentFixture<DocumentCardLargeComponent>; let fixture: ComponentFixture<DocumentCardLargeComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentCardLargeComponent ] declarations: [DocumentCardLargeComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentCardLargeComponent); fixture = TestBed.createComponent(DocumentCardLargeComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,20 +1,36 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import {
import { DomSanitizer } from '@angular/platform-browser'; Component,
import { PaperlessDocument } from 'src/app/data/paperless-document'; EventEmitter,
import { DocumentService } from 'src/app/services/rest/document.service'; Input,
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; OnInit,
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; Output,
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; ViewChild,
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'; } from '@angular/core'
import { DomSanitizer } from '@angular/platform-browser'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentService } from 'src/app/services/rest/document.service'
import {
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
@Component({ @Component({
selector: 'app-document-card-large', selector: 'app-document-card-large',
templateUrl: './document-card-large.component.html', templateUrl: './document-card-large.component.html',
styleUrls: ['./document-card-large.component.scss', '../popover-preview/popover-preview.scss'] styleUrls: [
'./document-card-large.component.scss',
'../popover-preview/popover-preview.scss',
],
}) })
export class DocumentCardLargeComponent implements OnInit { export class DocumentCardLargeComponent implements OnInit {
constructor(
constructor(private documentService: DocumentService, private sanitizer: DomSanitizer, private settingsService: SettingsService) { } private documentService: DocumentService,
private sanitizer: DomSanitizer,
private settingsService: SettingsService
) {}
@Input() @Input()
selected = false selected = false
@ -39,7 +55,7 @@ export class DocumentCardLargeComponent implements OnInit {
clickDocumentType = new EventEmitter<number>() clickDocumentType = new EventEmitter<number>()
@Output() @Output()
clickMoreLike= new EventEmitter() clickMoreLike = new EventEmitter()
@ViewChild('popover') popover: NgbPopover @ViewChild('popover') popover: NgbPopover
@ -49,17 +65,16 @@ export class DocumentCardLargeComponent implements OnInit {
get searchScoreClass() { get searchScoreClass() {
if (this.document.__search_hit__) { if (this.document.__search_hit__) {
if (this.document.__search_hit__.score > 0.7) { if (this.document.__search_hit__.score > 0.7) {
return "success" return 'success'
} else if (this.document.__search_hit__.score > 0.3) { } else if (this.document.__search_hit__.score > 0.3) {
return "warning" return 'warning'
} else { } else {
return "danger" return 'danger'
} }
} }
} }
ngOnInit(): void { ngOnInit(): void {}
}
getIsThumbInverted() { getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED) return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
@ -90,7 +105,7 @@ export class DocumentCardLargeComponent implements OnInit {
} else { } else {
this.popover.close() this.popover.close()
} }
}, 600); }, 600)
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentCardSmallComponent } from './document-card-small.component'; import { DocumentCardSmallComponent } from './document-card-small.component'
describe('DocumentCardSmallComponent', () => { describe('DocumentCardSmallComponent', () => {
let component: DocumentCardSmallComponent; let component: DocumentCardSmallComponent
let fixture: ComponentFixture<DocumentCardSmallComponent>; let fixture: ComponentFixture<DocumentCardSmallComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentCardSmallComponent ] declarations: [DocumentCardSmallComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentCardSmallComponent); fixture = TestBed.createComponent(DocumentCardSmallComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,18 +1,33 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import {
import { map } from 'rxjs/operators'; Component,
import { PaperlessDocument } from 'src/app/data/paperless-document'; EventEmitter,
import { DocumentService } from 'src/app/services/rest/document.service'; Input,
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; OnInit,
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; Output,
ViewChild,
} from '@angular/core'
import { map } from 'rxjs/operators'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { DocumentService } from 'src/app/services/rest/document.service'
import {
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
@Component({ @Component({
selector: 'app-document-card-small', selector: 'app-document-card-small',
templateUrl: './document-card-small.component.html', templateUrl: './document-card-small.component.html',
styleUrls: ['./document-card-small.component.scss', '../popover-preview/popover-preview.scss'] styleUrls: [
'./document-card-small.component.scss',
'../popover-preview/popover-preview.scss',
],
}) })
export class DocumentCardSmallComponent implements OnInit { export class DocumentCardSmallComponent implements OnInit {
constructor(
constructor(private documentService: DocumentService, private settingsService: SettingsService) { } private documentService: DocumentService,
private settingsService: SettingsService
) {}
@Input() @Input()
selected = false selected = false
@ -39,8 +54,7 @@ export class DocumentCardSmallComponent implements OnInit {
mouseOnPreview = false mouseOnPreview = false
popoverHidden = true popoverHidden = true
ngOnInit(): void { ngOnInit(): void {}
}
getIsThumbInverted() { getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED) return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
@ -60,7 +74,7 @@ export class DocumentCardSmallComponent implements OnInit {
getTagsLimited$() { getTagsLimited$() {
return this.document.tags$.pipe( return this.document.tags$.pipe(
map(tags => { map((tags) => {
if (tags.length > 7) { if (tags.length > 7) {
this.moreTags = tags.length - 6 this.moreTags = tags.length - 6
return tags.slice(0, 6) return tags.slice(0, 6)
@ -84,7 +98,7 @@ export class DocumentCardSmallComponent implements OnInit {
} else { } else {
this.popover.close() this.popover.close()
} }
}, 600); }, 600)
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentListComponent } from './document-list.component'; import { DocumentListComponent } from './document-list.component'
describe('DocumentListComponent', () => { describe('DocumentListComponent', () => {
let component: DocumentListComponent; let component: DocumentListComponent
let fixture: ComponentFixture<DocumentListComponent>; let fixture: ComponentFixture<DocumentListComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentListComponent ] declarations: [DocumentListComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentListComponent); fixture = TestBed.createComponent(DocumentListComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,27 +1,39 @@
import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core'; import {
import { ActivatedRoute, Router } from '@angular/router'; Component,
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; OnDestroy,
import { Subscription } from 'rxjs'; OnInit,
import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule'; QueryList,
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'; ViewChild,
import { PaperlessDocument } from 'src/app/data/paperless-document'; ViewChildren,
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; } from '@angular/core'
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive'; import { ActivatedRoute, Router } from '@angular/router'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { Subscription } from 'rxjs'
import { DOCUMENT_SORT_FIELDS, DOCUMENT_SORT_FIELDS_FULLTEXT } from 'src/app/services/rest/document.service'; import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'; import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
import { ToastService } from 'src/app/services/toast.service'; import { PaperlessDocument } from 'src/app/data/paperless-document'
import { FilterEditorComponent } from './filter-editor/filter-editor.component'; import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; import {
SortableDirective,
SortEvent,
} from 'src/app/directives/sortable.directive'
import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
DOCUMENT_SORT_FIELDS,
DOCUMENT_SORT_FIELDS_FULLTEXT,
} from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { ToastService } from 'src/app/services/toast.service'
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
@Component({ @Component({
selector: 'app-document-list', selector: 'app-document-list',
templateUrl: './document-list.component.html', templateUrl: './document-list.component.html',
styleUrls: ['./document-list.component.scss'] styleUrls: ['./document-list.component.scss'],
}) })
export class DocumentListComponent implements OnInit, OnDestroy { export class DocumentListComponent implements OnInit, OnDestroy {
constructor( constructor(
public list: DocumentListViewService, public list: DocumentListViewService,
public savedViewService: SavedViewService, public savedViewService: SavedViewService,
@ -30,12 +42,12 @@ export class DocumentListComponent implements OnInit, OnDestroy {
private toastService: ToastService, private toastService: ToastService,
private modalService: NgbModal, private modalService: NgbModal,
private consumerStatusService: ConsumerStatusService private consumerStatusService: ConsumerStatusService
) { } ) {}
@ViewChild("filterEditor") @ViewChild('filterEditor')
private filterEditor: FilterEditorComponent private filterEditor: FilterEditorComponent
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>; @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
displayMode = 'smallCards' // largeCards, smallCards, details displayMode = 'smallCards' // largeCards, smallCards, details
@ -52,7 +64,9 @@ export class DocumentListComponent implements OnInit, OnDestroy {
} }
getSortFields() { getSortFields() {
return isFullTextFilterRule(this.list.filterRules) ? DOCUMENT_SORT_FIELDS_FULLTEXT : DOCUMENT_SORT_FIELDS return isFullTextFilterRule(this.list.filterRules)
? DOCUMENT_SORT_FIELDS_FULLTEXT
: DOCUMENT_SORT_FIELDS
} }
onSort(event: SortEvent) { onSort(event: SortEvent) {
@ -71,14 +85,16 @@ export class DocumentListComponent implements OnInit, OnDestroy {
if (localStorage.getItem('document-list:displayMode') != null) { if (localStorage.getItem('document-list:displayMode') != null) {
this.displayMode = localStorage.getItem('document-list:displayMode') this.displayMode = localStorage.getItem('document-list:displayMode')
} }
this.consumptionFinishedSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(() => { this.consumptionFinishedSubscription = this.consumerStatusService
this.list.reload() .onDocumentConsumptionFinished()
}) .subscribe(() => {
this.route.paramMap.subscribe(params => { this.list.reload()
})
this.route.paramMap.subscribe((params) => {
if (params.has('id')) { if (params.has('id')) {
this.savedViewService.getCached(+params.get('id')).subscribe(view => { this.savedViewService.getCached(+params.get('id')).subscribe((view) => {
if (!view) { if (!view) {
this.router.navigate(["404"]) this.router.navigate(['404'])
return return
} }
this.list.activateSavedView(view) this.list.activateSavedView(view)
@ -110,19 +126,23 @@ export class DocumentListComponent implements OnInit, OnDestroy {
id: this.list.activeSavedViewId, id: this.list.activeSavedViewId,
filter_rules: this.list.filterRules, filter_rules: this.list.filterRules,
sort_field: this.list.sortField, sort_field: this.list.sortField,
sort_reverse: this.list.sortReverse sort_reverse: this.list.sortReverse,
} }
this.savedViewService.patch(savedView).subscribe(result => { this.savedViewService.patch(savedView).subscribe((result) => {
this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`) this.toastService.showInfo(
$localize`View "${this.list.activeSavedViewTitle}" saved successfully.`
)
this.unmodifiedFilterRules = this.list.filterRules this.unmodifiedFilterRules = this.list.filterRules
}) })
} }
} }
saveViewConfigAs() { saveViewConfigAs() {
let modal = this.modalService.open(SaveViewConfigDialogComponent, {backdrop: 'static'}) let modal = this.modalService.open(SaveViewConfigDialogComponent, {
backdrop: 'static',
})
modal.componentInstance.defaultName = this.filterEditor.generateFilterName() modal.componentInstance.defaultName = this.filterEditor.generateFilterName()
modal.componentInstance.saveClicked.subscribe(formValue => { modal.componentInstance.saveClicked.subscribe((formValue) => {
modal.componentInstance.buttonsEnabled = false modal.componentInstance.buttonsEnabled = false
let savedView: PaperlessSavedView = { let savedView: PaperlessSavedView = {
name: formValue.name, name: formValue.name,
@ -130,16 +150,21 @@ export class DocumentListComponent implements OnInit, OnDestroy {
show_in_sidebar: formValue.showInSideBar, show_in_sidebar: formValue.showInSideBar,
filter_rules: this.list.filterRules, filter_rules: this.list.filterRules,
sort_reverse: this.list.sortReverse, sort_reverse: this.list.sortReverse,
sort_field: this.list.sortField sort_field: this.list.sortField,
} }
this.savedViewService.create(savedView).subscribe(() => { this.savedViewService.create(savedView).subscribe(
modal.close() () => {
this.toastService.showInfo($localize`View "${savedView.name}" created successfully.`) modal.close()
}, error => { this.toastService.showInfo(
modal.componentInstance.error = error.error $localize`View "${savedView.name}" created successfully.`
modal.componentInstance.buttonsEnabled = true )
}) },
(error) => {
modal.componentInstance.error = error.error
modal.componentInstance.buttonsEnabled = true
}
)
}) })
} }
@ -170,7 +195,9 @@ export class DocumentListComponent implements OnInit, OnDestroy {
} }
clickMoreLike(documentID: number) { clickMoreLike(documentID: number) {
this.list.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString()}]) this.list.quickFilter([
{ rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString() },
])
} }
trackByDocumentId(index, item: PaperlessDocument) { trackByDocumentId(index, item: PaperlessDocument) {

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FilterEditorComponent } from './filter-editor.component'; import { FilterEditorComponent } from './filter-editor.component'
describe('FilterEditorComponent', () => { describe('FilterEditorComponent', () => {
let component: FilterEditorComponent; let component: FilterEditorComponent
let fixture: ComponentFixture<FilterEditorComponent>; let fixture: ComponentFixture<FilterEditorComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ FilterEditorComponent ] declarations: [FilterEditorComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FilterEditorComponent); fixture = TestBed.createComponent(FilterEditorComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,56 +1,85 @@
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import {
import { PaperlessTag } from 'src/app/data/paperless-tag'; Component,
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; EventEmitter,
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; Input,
import { Subject, Subscription } from 'rxjs'; Output,
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; OnInit,
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; OnDestroy,
import { TagService } from 'src/app/services/rest/tag.service'; ViewChild,
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; ElementRef,
import { FilterRule } from 'src/app/data/filter-rule'; } from '@angular/core'
import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_ASN, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY, FILTER_HAS_ANY_TAG, FILTER_HAS_TAGS_ALL, FILTER_HAS_TAGS_ANY, FILTER_DOES_NOT_HAVE_TAG, FILTER_TITLE, FILTER_TITLE_CONTENT } from 'src/app/data/filter-rule-type'; import { PaperlessTag } from 'src/app/data/paperless-tag'
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'; import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'; import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentService } from 'src/app/services/rest/document.service'; import { Subject, Subscription } from 'rxjs'
import { PaperlessDocument } from 'src/app/data/paperless-document'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { TagService } from 'src/app/services/rest/tag.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { FilterRule } from 'src/app/data/filter-rule'
import {
FILTER_ADDED_AFTER,
FILTER_ADDED_BEFORE,
FILTER_ASN,
FILTER_CORRESPONDENT,
FILTER_CREATED_AFTER,
FILTER_CREATED_BEFORE,
FILTER_DOCUMENT_TYPE,
FILTER_FULLTEXT_MORELIKE,
FILTER_FULLTEXT_QUERY,
FILTER_HAS_ANY_TAG,
FILTER_HAS_TAGS_ALL,
FILTER_HAS_TAGS_ANY,
FILTER_DOES_NOT_HAVE_TAG,
FILTER_TITLE,
FILTER_TITLE_CONTENT,
} from 'src/app/data/filter-rule-type'
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { DocumentService } from 'src/app/services/rest/document.service'
import { PaperlessDocument } from 'src/app/data/paperless-document'
const TEXT_FILTER_TARGET_TITLE = "title" const TEXT_FILTER_TARGET_TITLE = 'title'
const TEXT_FILTER_TARGET_TITLE_CONTENT = "title-content" const TEXT_FILTER_TARGET_TITLE_CONTENT = 'title-content'
const TEXT_FILTER_TARGET_ASN = "asn" const TEXT_FILTER_TARGET_ASN = 'asn'
const TEXT_FILTER_TARGET_FULLTEXT_QUERY = "fulltext-query" const TEXT_FILTER_TARGET_FULLTEXT_QUERY = 'fulltext-query'
const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = "fulltext-morelike" const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = 'fulltext-morelike'
@Component({ @Component({
selector: 'app-filter-editor', selector: 'app-filter-editor',
templateUrl: './filter-editor.component.html', templateUrl: './filter-editor.component.html',
styleUrls: ['./filter-editor.component.scss'] styleUrls: ['./filter-editor.component.scss'],
}) })
export class FilterEditorComponent implements OnInit, OnDestroy { export class FilterEditorComponent implements OnInit, OnDestroy {
generateFilterName() { generateFilterName() {
if (this.filterRules.length == 1) { if (this.filterRules.length == 1) {
let rule = this.filterRules[0] let rule = this.filterRules[0]
switch(this.filterRules[0].rule_type) { switch (this.filterRules[0].rule_type) {
case FILTER_CORRESPONDENT: case FILTER_CORRESPONDENT:
if (rule.value) { if (rule.value) {
return $localize`Correspondent: ${this.correspondents.find(c => c.id == +rule.value)?.name}` return $localize`Correspondent: ${
this.correspondents.find((c) => c.id == +rule.value)?.name
}`
} else { } else {
return $localize`Without correspondent` return $localize`Without correspondent`
} }
case FILTER_DOCUMENT_TYPE: case FILTER_DOCUMENT_TYPE:
if (rule.value) { if (rule.value) {
return $localize`Type: ${this.documentTypes.find(dt => dt.id == +rule.value)?.name}` return $localize`Type: ${
this.documentTypes.find((dt) => dt.id == +rule.value)?.name
}`
} else { } else {
return $localize`Without document type` return $localize`Without document type`
} }
case FILTER_HAS_TAGS_ALL: case FILTER_HAS_TAGS_ALL:
return $localize`Tag: ${this.tags.find(t => t.id == +rule.value)?.name}` return $localize`Tag: ${
this.tags.find((t) => t.id == +rule.value)?.name
}`
case FILTER_HAS_ANY_TAG: case FILTER_HAS_ANY_TAG:
if (rule.value == "false") { if (rule.value == 'false') {
return $localize`Without any tag` return $localize`Without any tag`
} }
@ -62,7 +91,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
} }
} }
return "" return ''
} }
constructor( constructor(
@ -70,28 +99,37 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
private tagService: TagService, private tagService: TagService,
private correspondentService: CorrespondentService, private correspondentService: CorrespondentService,
private documentService: DocumentService private documentService: DocumentService
) { } ) {}
@ViewChild("textFilterInput") @ViewChild('textFilterInput')
textFilterInput: ElementRef textFilterInput: ElementRef
tags: PaperlessTag[] = [] tags: PaperlessTag[] = []
correspondents: PaperlessCorrespondent[] = [] correspondents: PaperlessCorrespondent[] = []
documentTypes: PaperlessDocumentType[] = [] documentTypes: PaperlessDocumentType[] = []
_textFilter = "" _textFilter = ''
_moreLikeId: number _moreLikeId: number
_moreLikeDoc: PaperlessDocument _moreLikeDoc: PaperlessDocument
get textFilterTargets() { get textFilterTargets() {
let targets = [ let targets = [
{id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`}, { id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title` },
{id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`}, {
{id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN`}, id: TEXT_FILTER_TARGET_TITLE_CONTENT,
{id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, name: $localize`Advanced search`} name: $localize`Title & content`,
},
{ id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN` },
{
id: TEXT_FILTER_TARGET_FULLTEXT_QUERY,
name: $localize`Advanced search`,
},
] ]
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
targets.push({id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE, name: $localize`More like`}) targets.push({
id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE,
name: $localize`More like`,
})
} }
return targets return targets
} }
@ -99,10 +137,10 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
get textFilterTargetName() { get textFilterTargetName() {
return this.textFilterTargets.find(t => t.id == this.textFilterTarget)?.name return this.textFilterTargets.find((t) => t.id == this.textFilterTarget)
?.name
} }
tagSelectionModel = new FilterableDropdownSelectionModel() tagSelectionModel = new FilterableDropdownSelectionModel()
correspondentSelectionModel = new FilterableDropdownSelectionModel() correspondentSelectionModel = new FilterableDropdownSelectionModel()
documentTypeSelectionModel = new FilterableDropdownSelectionModel() documentTypeSelectionModel = new FilterableDropdownSelectionModel()
@ -126,7 +164,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
} }
@Input() @Input()
set filterRules (value: FilterRule[]) { set filterRules(value: FilterRule[]) {
this._filterRules = value this._filterRules = value
this.documentTypeSelectionModel.clear(false) this.documentTypeSelectionModel.clear(false)
@ -139,7 +177,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
this.dateCreatedBefore = null this.dateCreatedBefore = null
this.dateCreatedAfter = null this.dateCreatedAfter = null
value.forEach(rule => { value.forEach((rule) => {
switch (rule.rule_type) { switch (rule.rule_type) {
case FILTER_TITLE: case FILTER_TITLE:
this._textFilter = rule.value this._textFilter = rule.value
@ -160,7 +198,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
case FILTER_FULLTEXT_MORELIKE: case FILTER_FULLTEXT_MORELIKE:
this._moreLikeId = +rule.value this._moreLikeId = +rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
this.documentService.get(this._moreLikeId).subscribe(result => { this.documentService.get(this._moreLikeId).subscribe((result) => {
this._moreLikeDoc = result this._moreLikeDoc = result
this._textFilter = result.title this._textFilter = result.title
}) })
@ -178,23 +216,43 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
this.dateAddedBefore = rule.value this.dateAddedBefore = rule.value
break break
case FILTER_HAS_TAGS_ALL: case FILTER_HAS_TAGS_ALL:
this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) this.tagSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Selected,
false
)
break break
case FILTER_HAS_TAGS_ANY: case FILTER_HAS_TAGS_ANY:
this.tagSelectionModel.logicalOperator = 'or' this.tagSelectionModel.logicalOperator = 'or'
this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) this.tagSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Selected,
false
)
break break
case FILTER_HAS_ANY_TAG: case FILTER_HAS_ANY_TAG:
this.tagSelectionModel.set(null, ToggleableItemState.Selected, false) this.tagSelectionModel.set(null, ToggleableItemState.Selected, false)
break break
case FILTER_DOES_NOT_HAVE_TAG: case FILTER_DOES_NOT_HAVE_TAG:
this.tagSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Excluded, false) this.tagSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Excluded,
false
)
break break
case FILTER_CORRESPONDENT: case FILTER_CORRESPONDENT:
this.correspondentSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) this.correspondentSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Selected,
false
)
break break
case FILTER_DOCUMENT_TYPE: case FILTER_DOCUMENT_TYPE:
this.documentTypeSelectionModel.set(rule.value ? +rule.value : null, ToggleableItemState.Selected, false) this.documentTypeSelectionModel.set(
rule.value ? +rule.value : null,
ToggleableItemState.Selected,
false
)
break break
} }
}) })
@ -203,49 +261,104 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
get filterRules(): FilterRule[] { get filterRules(): FilterRule[] {
let filterRules: FilterRule[] = [] let filterRules: FilterRule[] = []
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT) { if (
filterRules.push({rule_type: FILTER_TITLE_CONTENT, value: this._textFilter}) this._textFilter &&
this.textFilterTarget == TEXT_FILTER_TARGET_TITLE_CONTENT
) {
filterRules.push({
rule_type: FILTER_TITLE_CONTENT,
value: this._textFilter,
})
} }
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE) { if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_TITLE) {
filterRules.push({rule_type: FILTER_TITLE, value: this._textFilter}) filterRules.push({ rule_type: FILTER_TITLE, value: this._textFilter })
} }
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_ASN) { if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_ASN) {
filterRules.push({rule_type: FILTER_ASN, value: this._textFilter}) filterRules.push({ rule_type: FILTER_ASN, value: this._textFilter })
} }
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY) { if (
filterRules.push({rule_type: FILTER_FULLTEXT_QUERY, value: this._textFilter}) this._textFilter &&
this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY
) {
filterRules.push({
rule_type: FILTER_FULLTEXT_QUERY,
value: this._textFilter,
})
} }
if (this._moreLikeId && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { if (
filterRules.push({rule_type: FILTER_FULLTEXT_MORELIKE, value: this._moreLikeId?.toString()}) this._moreLikeId &&
this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
) {
filterRules.push({
rule_type: FILTER_FULLTEXT_MORELIKE,
value: this._moreLikeId?.toString(),
})
} }
if (this.tagSelectionModel.isNoneSelected()) { if (this.tagSelectionModel.isNoneSelected()) {
filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"}) filterRules.push({ rule_type: FILTER_HAS_ANY_TAG, value: 'false' })
} else { } else {
const tagFilterType = this.tagSelectionModel.logicalOperator == 'and' ? FILTER_HAS_TAGS_ALL : FILTER_HAS_TAGS_ANY const tagFilterType =
this.tagSelectionModel.getSelectedItems().filter(tag => tag.id).forEach(tag => { this.tagSelectionModel.logicalOperator == 'and'
filterRules.push({rule_type: tagFilterType, value: tag.id?.toString()}) ? FILTER_HAS_TAGS_ALL
}) : FILTER_HAS_TAGS_ANY
this.tagSelectionModel.getExcludedItems().filter(tag => tag.id).forEach(tag => { this.tagSelectionModel
filterRules.push({rule_type: FILTER_DOES_NOT_HAVE_TAG, value: tag.id?.toString()}) .getSelectedItems()
}) .filter((tag) => tag.id)
.forEach((tag) => {
filterRules.push({
rule_type: tagFilterType,
value: tag.id?.toString(),
})
})
this.tagSelectionModel
.getExcludedItems()
.filter((tag) => tag.id)
.forEach((tag) => {
filterRules.push({
rule_type: FILTER_DOES_NOT_HAVE_TAG,
value: tag.id?.toString(),
})
})
} }
this.correspondentSelectionModel.getSelectedItems().forEach(correspondent => { this.correspondentSelectionModel
filterRules.push({rule_type: FILTER_CORRESPONDENT, value: correspondent.id?.toString()}) .getSelectedItems()
}) .forEach((correspondent) => {
this.documentTypeSelectionModel.getSelectedItems().forEach(documentType => { filterRules.push({
filterRules.push({rule_type: FILTER_DOCUMENT_TYPE, value: documentType.id?.toString()}) rule_type: FILTER_CORRESPONDENT,
}) value: correspondent.id?.toString(),
})
})
this.documentTypeSelectionModel
.getSelectedItems()
.forEach((documentType) => {
filterRules.push({
rule_type: FILTER_DOCUMENT_TYPE,
value: documentType.id?.toString(),
})
})
if (this.dateCreatedBefore) { if (this.dateCreatedBefore) {
filterRules.push({rule_type: FILTER_CREATED_BEFORE, value: this.dateCreatedBefore}) filterRules.push({
rule_type: FILTER_CREATED_BEFORE,
value: this.dateCreatedBefore,
})
} }
if (this.dateCreatedAfter) { if (this.dateCreatedAfter) {
filterRules.push({rule_type: FILTER_CREATED_AFTER, value: this.dateCreatedAfter}) filterRules.push({
rule_type: FILTER_CREATED_AFTER,
value: this.dateCreatedAfter,
})
} }
if (this.dateAddedBefore) { if (this.dateAddedBefore) {
filterRules.push({rule_type: FILTER_ADDED_BEFORE, value: this.dateAddedBefore}) filterRules.push({
rule_type: FILTER_ADDED_BEFORE,
value: this.dateAddedBefore,
})
} }
if (this.dateAddedAfter) { if (this.dateAddedAfter) {
filterRules.push({rule_type: FILTER_ADDED_AFTER, value: this.dateAddedAfter}) filterRules.push({
rule_type: FILTER_ADDED_AFTER,
value: this.dateAddedAfter,
})
} }
return filterRules return filterRules
} }
@ -260,14 +373,20 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
if (this._unmodifiedFilterRules.length != this._filterRules.length) { if (this._unmodifiedFilterRules.length != this._filterRules.length) {
modified = true modified = true
} else { } else {
modified = this._unmodifiedFilterRules.some(rule => { modified = this._unmodifiedFilterRules.some((rule) => {
return (this._filterRules.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined) return (
this._filterRules.find(
(fri) => fri.rule_type == rule.rule_type && fri.value == rule.value
) == undefined
)
}) })
if (!modified) { if (!modified) {
// only check other direction if we havent already determined is modified // only check other direction if we havent already determined is modified
modified = this._filterRules.some(rule => { modified = this._filterRules.some((rule) => {
this._unmodifiedFilterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined this._unmodifiedFilterRules.find(
(fr) => fr.rule_type == rule.rule_type && fr.value == rule.value
) == undefined
}) })
} }
} }
@ -290,23 +409,27 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
subscription: Subscription subscription: Subscription
ngOnInit() { ngOnInit() {
this.tagService.listAll().subscribe(result => this.tags = result.results) this.tagService
this.correspondentService.listAll().subscribe(result => this.correspondents = result.results) .listAll()
this.documentTypeService.listAll().subscribe(result => this.documentTypes = result.results) .subscribe((result) => (this.tags = result.results))
this.correspondentService
.listAll()
.subscribe((result) => (this.correspondents = result.results))
this.documentTypeService
.listAll()
.subscribe((result) => (this.documentTypes = result.results))
this.textFilterDebounce = new Subject<string>() this.textFilterDebounce = new Subject<string>()
this.subscription = this.textFilterDebounce.pipe( this.subscription = this.textFilterDebounce
debounceTime(400), .pipe(debounceTime(400), distinctUntilChanged())
distinctUntilChanged() .subscribe((text) => {
).subscribe(text => { this._textFilter = text
this._textFilter = text this.documentService.searchQuery = text
this.documentService.searchQuery = text this.updateRules()
this.updateRules() })
})
if (this._textFilter) this.documentService.searchQuery = this._textFilter if (this._textFilter) this.documentService.searchQuery = this._textFilter
} }
ngOnDestroy() { ngOnDestroy() {
@ -324,11 +447,17 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
} }
addCorrespondent(correspondentId: number) { addCorrespondent(correspondentId: number) {
this.correspondentSelectionModel.set(correspondentId, ToggleableItemState.Selected) this.correspondentSelectionModel.set(
correspondentId,
ToggleableItemState.Selected
)
} }
addDocumentType(documentTypeId: number) { addDocumentType(documentTypeId: number) {
this.documentTypeSelectionModel.set(documentTypeId, ToggleableItemState.Selected) this.documentTypeSelectionModel.set(
documentTypeId,
ToggleableItemState.Selected
)
} }
onTagsDropdownOpen() { onTagsDropdownOpen() {
@ -344,8 +473,11 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
} }
changeTextFilterTarget(target) { changeTextFilterTarget(target) {
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE && target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) { if (
this._textFilter = "" this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE &&
target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
) {
this._textFilter = ''
} }
this.textFilterTarget = target this.textFilterTarget = target
this.textFilterInput.nativeElement.focus() this.textFilterInput.nativeElement.focus()

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'; import { SaveViewConfigDialogComponent } from './save-view-config-dialog.component'
describe('SaveViewConfigDialogComponent', () => { describe('SaveViewConfigDialogComponent', () => {
let component: SaveViewConfigDialogComponent; let component: SaveViewConfigDialogComponent
let fixture: ComponentFixture<SaveViewConfigDialogComponent>; let fixture: ComponentFixture<SaveViewConfigDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SaveViewConfigDialogComponent ] declarations: [SaveViewConfigDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SaveViewConfigDialogComponent); fixture = TestBed.createComponent(SaveViewConfigDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,14 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
@Component({ @Component({
selector: 'app-save-view-config-dialog', selector: 'app-save-view-config-dialog',
templateUrl: './save-view-config-dialog.component.html', templateUrl: './save-view-config-dialog.component.html',
styleUrls: ['./save-view-config-dialog.component.scss'] styleUrls: ['./save-view-config-dialog.component.scss'],
}) })
export class SaveViewConfigDialogComponent implements OnInit { export class SaveViewConfigDialogComponent implements OnInit {
constructor(private modal: NgbActiveModal) {}
constructor(private modal: NgbActiveModal) { }
@Output() @Output()
public saveClicked = new EventEmitter() public saveClicked = new EventEmitter()
@ -22,7 +21,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
closeEnabled = false closeEnabled = false
_defaultName = "" _defaultName = ''
get defaultName() { get defaultName() {
return this._defaultName return this._defaultName
@ -31,7 +30,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
@Input() @Input()
set defaultName(value: string) { set defaultName(value: string) {
this._defaultName = value this._defaultName = value
this.saveViewConfigForm.patchValue({name: value}) this.saveViewConfigForm.patchValue({ name: value })
} }
saveViewConfigForm = new FormGroup({ saveViewConfigForm = new FormGroup({
@ -44,7 +43,7 @@ export class SaveViewConfigDialogComponent implements OnInit {
// wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM // wait to enable close button so it doesnt steal focus from input since its the first clickable element in the DOM
setTimeout(() => { setTimeout(() => {
this.closeEnabled = true this.closeEnabled = true
}); })
} }
save() { save() {

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'; import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'
describe('CorrespondentEditDialogComponent', () => { describe('CorrespondentEditDialogComponent', () => {
let component: CorrespondentEditDialogComponent; let component: CorrespondentEditDialogComponent
let fixture: ComponentFixture<CorrespondentEditDialogComponent>; let fixture: ComponentFixture<CorrespondentEditDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ CorrespondentEditDialogComponent ] declarations: [CorrespondentEditDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CorrespondentEditDialogComponent); fixture = TestBed.createComponent(CorrespondentEditDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,19 +1,22 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'; import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
@Component({ @Component({
selector: 'app-correspondent-edit-dialog', selector: 'app-correspondent-edit-dialog',
templateUrl: './correspondent-edit-dialog.component.html', templateUrl: './correspondent-edit-dialog.component.html',
styleUrls: ['./correspondent-edit-dialog.component.scss'] styleUrls: ['./correspondent-edit-dialog.component.scss'],
}) })
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> { export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
constructor(
constructor(service: CorrespondentService, activeModal: NgbActiveModal, toastService: ToastService) { service: CorrespondentService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService) super(service, activeModal, toastService)
} }
@ -29,9 +32,8 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl
return new FormGroup({ return new FormGroup({
name: new FormControl(''), name: new FormControl(''),
matching_algorithm: new FormControl(1), matching_algorithm: new FormControl(1),
match: new FormControl(""), match: new FormControl(''),
is_insensitive: new FormControl(true) is_insensitive: new FormControl(true),
}) })
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { CorrespondentListComponent } from './correspondent-list.component'; import { CorrespondentListComponent } from './correspondent-list.component'
describe('CorrespondentListComponent', () => { describe('CorrespondentListComponent', () => {
let component: CorrespondentListComponent; let component: CorrespondentListComponent
let fixture: ComponentFixture<CorrespondentListComponent>; let fixture: ComponentFixture<CorrespondentListComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ CorrespondentListComponent ] declarations: [CorrespondentListComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CorrespondentListComponent); fixture = TestBed.createComponent(CorrespondentListComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,25 +1,31 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'; import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'; import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'; import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
import { GenericListComponent } from '../generic-list/generic-list.component'; import { GenericListComponent } from '../generic-list/generic-list.component'
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'; import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/correspondent-edit-dialog.component'
@Component({ @Component({
selector: 'app-correspondent-list', selector: 'app-correspondent-list',
templateUrl: './correspondent-list.component.html', templateUrl: './correspondent-list.component.html',
styleUrls: ['./correspondent-list.component.scss'] styleUrls: ['./correspondent-list.component.scss'],
}) })
export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> { export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> {
constructor(
constructor(correspondentsService: CorrespondentService, modalService: NgbModal, correspondentsService: CorrespondentService,
modalService: NgbModal,
private list: DocumentListViewService, private list: DocumentListViewService,
toastService: ToastService toastService: ToastService
) { ) {
super(correspondentsService,modalService,CorrespondentEditDialogComponent, toastService) super(
correspondentsService,
modalService,
CorrespondentEditDialogComponent,
toastService
)
} }
getDeleteMessage(object: PaperlessCorrespondent) { getDeleteMessage(object: PaperlessCorrespondent) {
@ -27,6 +33,8 @@ export class CorrespondentListComponent extends GenericListComponent<PaperlessCo
} }
filterDocuments(object: PaperlessCorrespondent) { filterDocuments(object: PaperlessCorrespondent) {
this.list.quickFilter([{rule_type: FILTER_CORRESPONDENT, value: object.id.toString()}]) this.list.quickFilter([
{ rule_type: FILTER_CORRESPONDENT, value: object.id.toString() },
])
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'; import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'
describe('DocumentTypeEditDialogComponent', () => { describe('DocumentTypeEditDialogComponent', () => {
let component: DocumentTypeEditDialogComponent; let component: DocumentTypeEditDialogComponent
let fixture: ComponentFixture<DocumentTypeEditDialogComponent>; let fixture: ComponentFixture<DocumentTypeEditDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentTypeEditDialogComponent ] declarations: [DocumentTypeEditDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentTypeEditDialogComponent); fixture = TestBed.createComponent(DocumentTypeEditDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,19 +1,22 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'; import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
@Component({ @Component({
selector: 'app-document-type-edit-dialog', selector: 'app-document-type-edit-dialog',
templateUrl: './document-type-edit-dialog.component.html', templateUrl: './document-type-edit-dialog.component.html',
styleUrls: ['./document-type-edit-dialog.component.scss'] styleUrls: ['./document-type-edit-dialog.component.scss'],
}) })
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> { export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
constructor(
constructor(service: DocumentTypeService, activeModal: NgbActiveModal, toastService: ToastService) { service: DocumentTypeService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService) super(service, activeModal, toastService)
} }
@ -29,9 +32,8 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle
return new FormGroup({ return new FormGroup({
name: new FormControl(''), name: new FormControl(''),
matching_algorithm: new FormControl(1), matching_algorithm: new FormControl(1),
match: new FormControl(""), match: new FormControl(''),
is_insensitive: new FormControl(true) is_insensitive: new FormControl(true),
}) })
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DocumentTypeListComponent } from './document-type-list.component'; import { DocumentTypeListComponent } from './document-type-list.component'
describe('DocumentTypeListComponent', () => { describe('DocumentTypeListComponent', () => {
let component: DocumentTypeListComponent; let component: DocumentTypeListComponent
let fixture: ComponentFixture<DocumentTypeListComponent>; let fixture: ComponentFixture<DocumentTypeListComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ DocumentTypeListComponent ] declarations: [DocumentTypeListComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(DocumentTypeListComponent); fixture = TestBed.createComponent(DocumentTypeListComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,21 +1,22 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'; import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'; import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'; import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
import { GenericListComponent } from '../generic-list/generic-list.component'; import { GenericListComponent } from '../generic-list/generic-list.component'
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'; import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/document-type-edit-dialog.component'
@Component({ @Component({
selector: 'app-document-type-list', selector: 'app-document-type-list',
templateUrl: './document-type-list.component.html', templateUrl: './document-type-list.component.html',
styleUrls: ['./document-type-list.component.scss'] styleUrls: ['./document-type-list.component.scss'],
}) })
export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> { export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> {
constructor(
constructor(service: DocumentTypeService, modalService: NgbModal, service: DocumentTypeService,
modalService: NgbModal,
private list: DocumentListViewService, private list: DocumentListViewService,
toastService: ToastService toastService: ToastService
) { ) {
@ -26,8 +27,9 @@ export class DocumentTypeListComponent extends GenericListComponent<PaperlessDoc
return $localize`Do you really want to delete the document type "${object.name}"?` return $localize`Do you really want to delete the document type "${object.name}"?`
} }
filterDocuments(object: PaperlessDocumentType) { filterDocuments(object: PaperlessDocumentType) {
this.list.quickFilter([{rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString()}]) this.list.quickFilter([
{ rule_type: FILTER_DOCUMENT_TYPE, value: object.id.toString() },
])
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { GenericListComponent } from './generic-list.component'; import { GenericListComponent } from './generic-list.component'
describe('GenericListComponent', () => { describe('GenericListComponent', () => {
let component: GenericListComponent; let component: GenericListComponent
let fixture: ComponentFixture<GenericListComponent>; let fixture: ComponentFixture<GenericListComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ GenericListComponent ] declarations: [GenericListComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(GenericListComponent); fixture = TestBed.createComponent(GenericListComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,25 +1,39 @@
import { Directive, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'; import {
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; Directive,
import { Subject, Subscription } from 'rxjs'; OnDestroy,
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; OnInit,
import { MatchingModel, MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'; QueryList,
import { ObjectWithId } from 'src/app/data/object-with-id'; ViewChildren,
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive'; } from '@angular/core'
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ToastService } from 'src/app/services/toast.service'; import { Subject, Subscription } from 'rxjs'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
import {
MatchingModel,
MATCHING_ALGORITHMS,
MATCH_AUTO,
} from 'src/app/data/matching-model'
import { ObjectWithId } from 'src/app/data/object-with-id'
import {
SortableDirective,
SortEvent,
} from 'src/app/directives/sortable.directive'
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
@Directive() @Directive()
export abstract class GenericListComponent<T extends ObjectWithId> implements OnInit, OnDestroy { export abstract class GenericListComponent<T extends ObjectWithId>
implements OnInit, OnDestroy
{
constructor( constructor(
private service: AbstractNameFilterService<T>, private service: AbstractNameFilterService<T>,
private modalService: NgbModal, private modalService: NgbModal,
private editDialogComponent: any, private editDialogComponent: any,
private toastService: ToastService) { private toastService: ToastService
} ) {}
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>; @ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
public data: T[] = [] public data: T[] = []
@ -38,9 +52,11 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
if (o.matching_algorithm == MATCH_AUTO) { if (o.matching_algorithm == MATCH_AUTO) {
return $localize`Automatic` return $localize`Automatic`
} else if (o.match && o.match.length > 0) { } else if (o.match && o.match.length > 0) {
return `${MATCHING_ALGORITHMS.find(a => a.id == o.matching_algorithm).shortName}: ${o.match}` return `${
MATCHING_ALGORITHMS.find((a) => a.id == o.matching_algorithm).shortName
}: ${o.match}`
} else { } else {
return "-" return '-'
} }
} }
@ -50,20 +66,18 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
this.reloadData() this.reloadData()
} }
ngOnInit(): void { ngOnInit(): void {
this.reloadData() this.reloadData()
this.nameFilterDebounce = new Subject<string>() this.nameFilterDebounce = new Subject<string>()
this.subscription = this.nameFilterDebounce.pipe( this.subscription = this.nameFilterDebounce
debounceTime(400), .pipe(debounceTime(400), distinctUntilChanged())
distinctUntilChanged() .subscribe((title) => {
).subscribe(title => { this._nameFilter = title
this._nameFilter = title this.page = 1
this.page = 1 this.reloadData()
this.reloadData() })
})
} }
ngOnDestroy() { ngOnDestroy() {
@ -71,25 +85,37 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
} }
reloadData() { reloadData() {
this.service.listFiltered(this.page, null, this.sortField, this.sortReverse, this._nameFilter).subscribe(c => { this.service
this.data = c.results .listFiltered(
this.collectionSize = c.count this.page,
}); null,
this.sortField,
this.sortReverse,
this._nameFilter
)
.subscribe((c) => {
this.data = c.results
this.collectionSize = c.count
})
} }
openCreateDialog() { openCreateDialog() {
var activeModal = this.modalService.open(this.editDialogComponent, {backdrop: 'static'}) var activeModal = this.modalService.open(this.editDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.dialogMode = 'create' activeModal.componentInstance.dialogMode = 'create'
activeModal.componentInstance.success.subscribe(o => { activeModal.componentInstance.success.subscribe((o) => {
this.reloadData() this.reloadData()
}) })
} }
openEditDialog(object: T) { openEditDialog(object: T) {
var activeModal = this.modalService.open(this.editDialogComponent, {backdrop: 'static'}) var activeModal = this.modalService.open(this.editDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.object = object activeModal.componentInstance.object = object
activeModal.componentInstance.dialogMode = 'edit' activeModal.componentInstance.dialogMode = 'edit'
activeModal.componentInstance.success.subscribe(o => { activeModal.componentInstance.success.subscribe((o) => {
this.reloadData() this.reloadData()
}) })
} }
@ -99,23 +125,31 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
} }
openDeleteDialog(object: T) { openDeleteDialog(object: T) {
var activeModal = this.modalService.open(ConfirmDialogComponent, {backdrop: 'static'}) var activeModal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
activeModal.componentInstance.title = $localize`Confirm delete` activeModal.componentInstance.title = $localize`Confirm delete`
activeModal.componentInstance.messageBold = this.getDeleteMessage(object) activeModal.componentInstance.messageBold = this.getDeleteMessage(object)
activeModal.componentInstance.message = $localize`Associated documents will not be deleted.` activeModal.componentInstance.message = $localize`Associated documents will not be deleted.`
activeModal.componentInstance.btnClass = "btn-danger" activeModal.componentInstance.btnClass = 'btn-danger'
activeModal.componentInstance.btnCaption = $localize`Delete` activeModal.componentInstance.btnCaption = $localize`Delete`
activeModal.componentInstance.confirmClicked.subscribe(() => { activeModal.componentInstance.confirmClicked.subscribe(() => {
activeModal.componentInstance.buttonsEnabled = false activeModal.componentInstance.buttonsEnabled = false
this.service.delete(object).subscribe(_ => { this.service.delete(object).subscribe(
activeModal.close() (_) => {
this.reloadData() activeModal.close()
}, error => { this.reloadData()
activeModal.componentInstance.buttonsEnabled = true },
this.toastService.showError($localize`Error while deleting element: ${JSON.stringify(error.error)}`) (error) => {
}) activeModal.componentInstance.buttonsEnabled = true
} this.toastService.showError(
) $localize`Error while deleting element: ${JSON.stringify(
error.error
)}`
)
}
)
})
} }
get nameFilter() { get nameFilter() {
@ -125,7 +159,7 @@ export abstract class GenericListComponent<T extends ObjectWithId> implements On
set nameFilter(nameFilter: string) { set nameFilter(nameFilter: string) {
this.nameFilterDebounce.next(nameFilter) this.nameFilterDebounce.next(nameFilter)
} }
onNameFilterKeyUp(event: KeyboardEvent) { onNameFilterKeyUp(event: KeyboardEvent) {
if (event.code == 'Escape') this.nameFilterDebounce.next(null) if (event.code == 'Escape') this.nameFilterDebounce.next(null)
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { LogsComponent } from './logs.component'; import { LogsComponent } from './logs.component'
describe('LogsComponent', () => { describe('LogsComponent', () => {
let component: LogsComponent; let component: LogsComponent
let fixture: ComponentFixture<LogsComponent>; let fixture: ComponentFixture<LogsComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ LogsComponent ] declarations: [LogsComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(LogsComponent); fixture = TestBed.createComponent(LogsComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,14 +1,19 @@
import { Component, ElementRef, OnInit, AfterViewChecked, ViewChild } from '@angular/core'; import {
import { LogService } from 'src/app/services/rest/log.service'; Component,
ElementRef,
OnInit,
AfterViewChecked,
ViewChild,
} from '@angular/core'
import { LogService } from 'src/app/services/rest/log.service'
@Component({ @Component({
selector: 'app-logs', selector: 'app-logs',
templateUrl: './logs.component.html', templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'] styleUrls: ['./logs.component.scss'],
}) })
export class LogsComponent implements OnInit, AfterViewChecked { export class LogsComponent implements OnInit, AfterViewChecked {
constructor(private logService: LogService) {}
constructor(private logService: LogService) { }
logs: string[] = [] logs: string[] = []
@ -19,7 +24,7 @@ export class LogsComponent implements OnInit, AfterViewChecked {
@ViewChild('logContainer') logContainer: ElementRef @ViewChild('logContainer') logContainer: ElementRef
ngOnInit(): void { ngOnInit(): void {
this.logService.list().subscribe(result => { this.logService.list().subscribe((result) => {
this.logFiles = result this.logFiles = result
if (this.logFiles.length > 0) { if (this.logFiles.length > 0) {
this.activeLog = this.logFiles[0] this.activeLog = this.logFiles[0]
@ -29,25 +34,28 @@ export class LogsComponent implements OnInit, AfterViewChecked {
} }
ngAfterViewChecked() { ngAfterViewChecked() {
this.scrollToBottom(); this.scrollToBottom()
} }
reloadLogs() { reloadLogs() {
this.logService.get(this.activeLog).subscribe(result => { this.logService.get(this.activeLog).subscribe(
this.logs = result (result) => {
}, error => { this.logs = result
this.logs = [] },
}) (error) => {
this.logs = []
}
)
} }
getLogLevel(log: string) { getLogLevel(log: string) {
if (log.indexOf("[DEBUG]") != -1) { if (log.indexOf('[DEBUG]') != -1) {
return 10 return 10
} else if (log.indexOf("[WARNING]") != -1) { } else if (log.indexOf('[WARNING]') != -1) {
return 30 return 30
} else if (log.indexOf("[ERROR]") != -1) { } else if (log.indexOf('[ERROR]') != -1) {
return 40 return 40
} else if (log.indexOf("[CRITICAL]") != -1) { } else if (log.indexOf('[CRITICAL]') != -1) {
return 50 return 50
} else { } else {
return 20 return 20
@ -58,8 +66,7 @@ export class LogsComponent implements OnInit, AfterViewChecked {
this.logContainer?.nativeElement.scroll({ this.logContainer?.nativeElement.scroll({
top: this.logContainer.nativeElement.scrollHeight, top: this.logContainer.nativeElement.scrollHeight,
left: 0, left: 0,
behavior: 'auto' behavior: 'auto',
}); })
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SettingsComponent } from './settings.component'; import { SettingsComponent } from './settings.component'
describe('SettingsComponent', () => { describe('SettingsComponent', () => {
let component: SettingsComponent; let component: SettingsComponent
let fixture: ComponentFixture<SettingsComponent>; let fixture: ComponentFixture<SettingsComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ SettingsComponent ] declarations: [SettingsComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(SettingsComponent); fixture = TestBed.createComponent(SettingsComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,39 +1,49 @@
import { Component, Inject, LOCALE_ID, OnInit, OnDestroy, Renderer2 } from '@angular/core'; import {
import { FormControl, FormGroup } from '@angular/forms'; Component,
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'; Inject,
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; LOCALE_ID,
import { SavedViewService } from 'src/app/services/rest/saved-view.service'; OnInit,
import { LanguageOption, SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; OnDestroy,
import { ToastService } from 'src/app/services/toast.service'; Renderer2,
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'; } from '@angular/core'
import { Observable, Subscription, BehaviorSubject } from 'rxjs'; import { FormControl, FormGroup } from '@angular/forms'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import {
LanguageOption,
SettingsService,
SETTINGS_KEYS,
} from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
import { Observable, Subscription, BehaviorSubject } from 'rxjs'
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
templateUrl: './settings.component.html', templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'] styleUrls: ['./settings.component.scss'],
}) })
export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent { export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
savedViewGroup = new FormGroup({}) savedViewGroup = new FormGroup({})
settingsForm = new FormGroup({ settingsForm = new FormGroup({
'bulkEditConfirmationDialogs': new FormControl(null), bulkEditConfirmationDialogs: new FormControl(null),
'bulkEditApplyOnClose': new FormControl(null), bulkEditApplyOnClose: new FormControl(null),
'documentListItemPerPage': new FormControl(null), documentListItemPerPage: new FormControl(null),
'darkModeUseSystem': new FormControl(null), darkModeUseSystem: new FormControl(null),
'darkModeEnabled': new FormControl(null), darkModeEnabled: new FormControl(null),
'darkModeInvertThumbs': new FormControl(null), darkModeInvertThumbs: new FormControl(null),
'themeColor': new FormControl(null), themeColor: new FormControl(null),
'useNativePdfViewer': new FormControl(null), useNativePdfViewer: new FormControl(null),
'savedViews': this.savedViewGroup, savedViews: this.savedViewGroup,
'displayLanguage': new FormControl(null), displayLanguage: new FormControl(null),
'dateLocale': new FormControl(null), dateLocale: new FormControl(null),
'dateFormat': new FormControl(null), dateFormat: new FormControl(null),
'notificationsConsumerNewDocument': new FormControl(null), notificationsConsumerNewDocument: new FormControl(null),
'notificationsConsumerSuccess': new FormControl(null), notificationsConsumerSuccess: new FormControl(null),
'notificationsConsumerFailed': new FormControl(null), notificationsConsumerFailed: new FormControl(null),
'notificationsConsumerSuppressOnDashboard': new FormControl(null), notificationsConsumerSuppressOnDashboard: new FormControl(null),
}) })
savedViews: PaperlessSavedView[] savedViews: PaperlessSavedView[]
@ -44,7 +54,11 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
isDirty: Boolean = false isDirty: Boolean = false
get computedDateLocale(): string { get computedDateLocale(): string {
return this.settingsForm.value.dateLocale || this.settingsForm.value.displayLanguage || this.currentLocale return (
this.settingsForm.value.dateLocale ||
this.settingsForm.value.displayLanguage ||
this.currentLocale
)
} }
constructor( constructor(
@ -53,48 +67,71 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
private toastService: ToastService, private toastService: ToastService,
private settings: SettingsService, private settings: SettingsService,
@Inject(LOCALE_ID) public currentLocale: string @Inject(LOCALE_ID) public currentLocale: string
) { } ) {}
ngOnInit() { ngOnInit() {
this.savedViewService.listAll().subscribe(r => { this.savedViewService.listAll().subscribe((r) => {
this.savedViews = r.results this.savedViews = r.results
let storeData = { let storeData = {
'bulkEditConfirmationDialogs': this.settings.get(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS), bulkEditConfirmationDialogs: this.settings.get(
'bulkEditApplyOnClose': this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE), SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS
'documentListItemPerPage': this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE), ),
'darkModeUseSystem': this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM), bulkEditApplyOnClose: this.settings.get(
'darkModeEnabled': this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED), SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE
'darkModeInvertThumbs': this.settings.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED), ),
'themeColor': this.settings.get(SETTINGS_KEYS.THEME_COLOR), documentListItemPerPage: this.settings.get(
'useNativePdfViewer': this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER), SETTINGS_KEYS.DOCUMENT_LIST_SIZE
'savedViews': {}, ),
'displayLanguage': this.settings.getLanguage(), darkModeUseSystem: this.settings.get(
'dateLocale': this.settings.get(SETTINGS_KEYS.DATE_LOCALE), SETTINGS_KEYS.DARK_MODE_USE_SYSTEM
'dateFormat': this.settings.get(SETTINGS_KEYS.DATE_FORMAT), ),
'notificationsConsumerNewDocument': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT), darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED),
'notificationsConsumerSuccess': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS), darkModeInvertThumbs: this.settings.get(
'notificationsConsumerFailed': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED), SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED
'notificationsConsumerSuppressOnDashboard': this.settings.get(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD), ),
themeColor: this.settings.get(SETTINGS_KEYS.THEME_COLOR),
useNativePdfViewer: this.settings.get(
SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER
),
savedViews: {},
displayLanguage: this.settings.getLanguage(),
dateLocale: this.settings.get(SETTINGS_KEYS.DATE_LOCALE),
dateFormat: this.settings.get(SETTINGS_KEYS.DATE_FORMAT),
notificationsConsumerNewDocument: this.settings.get(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT
),
notificationsConsumerSuccess: this.settings.get(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS
),
notificationsConsumerFailed: this.settings.get(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED
),
notificationsConsumerSuppressOnDashboard: this.settings.get(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD
),
} }
for (let view of this.savedViews) { for (let view of this.savedViews) {
storeData.savedViews[view.id.toString()] = { storeData.savedViews[view.id.toString()] = {
"id": view.id, id: view.id,
"name": view.name, name: view.name,
"show_on_dashboard": view.show_on_dashboard, show_on_dashboard: view.show_on_dashboard,
"show_in_sidebar": view.show_in_sidebar show_in_sidebar: view.show_in_sidebar,
} }
this.savedViewGroup.addControl(view.id.toString(), new FormGroup({ this.savedViewGroup.addControl(
"id": new FormControl(null), view.id.toString(),
"name": new FormControl(null), new FormGroup({
"show_on_dashboard": new FormControl(null), id: new FormControl(null),
"show_in_sidebar": new FormControl(null) name: new FormControl(null),
})) show_on_dashboard: new FormControl(null),
show_in_sidebar: new FormControl(null),
})
)
} }
this.store = new BehaviorSubject(storeData) this.store = new BehaviorSubject(storeData)
this.storeSub = this.store.asObservable().subscribe(state => { this.storeSub = this.store.asObservable().subscribe((state) => {
this.settingsForm.patchValue(state, { emitEvent: false }) this.settingsForm.patchValue(state, { emitEvent: false })
}) })
@ -102,45 +139,93 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable()) this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable())
// Record dirty in case we need to 'undo' appearance settings if not saved on close // Record dirty in case we need to 'undo' appearance settings if not saved on close
this.isDirty$.subscribe(dirty => { this.isDirty$.subscribe((dirty) => {
this.isDirty = dirty this.isDirty = dirty
}) })
// "Live" visual changes prior to save // "Live" visual changes prior to save
this.settingsForm.valueChanges.subscribe(() => { this.settingsForm.valueChanges.subscribe(() => {
this.settings.updateAppearanceSettings(this.settingsForm.get('darkModeUseSystem').value, this.settingsForm.get('darkModeEnabled').value, this.settingsForm.get('themeColor').value) this.settings.updateAppearanceSettings(
this.settingsForm.get('darkModeUseSystem').value,
this.settingsForm.get('darkModeEnabled').value,
this.settingsForm.get('themeColor').value
)
}) })
}) })
} }
ngOnDestroy() { ngOnDestroy() {
if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save
this.storeSub && this.storeSub.unsubscribe(); this.storeSub && this.storeSub.unsubscribe()
} }
deleteSavedView(savedView: PaperlessSavedView) { deleteSavedView(savedView: PaperlessSavedView) {
this.savedViewService.delete(savedView).subscribe(() => { this.savedViewService.delete(savedView).subscribe(() => {
this.savedViewGroup.removeControl(savedView.id.toString()) this.savedViewGroup.removeControl(savedView.id.toString())
this.savedViews.splice(this.savedViews.indexOf(savedView), 1) this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
this.toastService.showInfo($localize`Saved view "${savedView.name}" deleted.`) this.toastService.showInfo(
$localize`Saved view "${savedView.name}" deleted.`
)
}) })
} }
private saveLocalSettings() { private saveLocalSettings() {
this.settings.set(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE, this.settingsForm.value.bulkEditApplyOnClose) this.settings.set(
this.settings.set(SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS, this.settingsForm.value.bulkEditConfirmationDialogs) SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
this.settings.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, this.settingsForm.value.documentListItemPerPage) this.settingsForm.value.bulkEditApplyOnClose
this.settings.set(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM, this.settingsForm.value.darkModeUseSystem) )
this.settings.set(SETTINGS_KEYS.DARK_MODE_ENABLED, (this.settingsForm.value.darkModeEnabled == true).toString()) this.settings.set(
this.settings.set(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED, (this.settingsForm.value.darkModeInvertThumbs == true).toString()) SETTINGS_KEYS.BULK_EDIT_CONFIRMATION_DIALOGS,
this.settings.set(SETTINGS_KEYS.THEME_COLOR, (this.settingsForm.value.themeColor).toString()) this.settingsForm.value.bulkEditConfirmationDialogs
this.settings.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, this.settingsForm.value.useNativePdfViewer) )
this.settings.set(SETTINGS_KEYS.DATE_LOCALE, this.settingsForm.value.dateLocale) this.settings.set(
this.settings.set(SETTINGS_KEYS.DATE_FORMAT, this.settingsForm.value.dateFormat) SETTINGS_KEYS.DOCUMENT_LIST_SIZE,
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT, this.settingsForm.value.notificationsConsumerNewDocument) this.settingsForm.value.documentListItemPerPage
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS, this.settingsForm.value.notificationsConsumerSuccess) )
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED, this.settingsForm.value.notificationsConsumerFailed) this.settings.set(
this.settings.set(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD, this.settingsForm.value.notificationsConsumerSuppressOnDashboard) SETTINGS_KEYS.DARK_MODE_USE_SYSTEM,
this.settingsForm.value.darkModeUseSystem
)
this.settings.set(
SETTINGS_KEYS.DARK_MODE_ENABLED,
(this.settingsForm.value.darkModeEnabled == true).toString()
)
this.settings.set(
SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED,
(this.settingsForm.value.darkModeInvertThumbs == true).toString()
)
this.settings.set(
SETTINGS_KEYS.THEME_COLOR,
this.settingsForm.value.themeColor.toString()
)
this.settings.set(
SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER,
this.settingsForm.value.useNativePdfViewer
)
this.settings.set(
SETTINGS_KEYS.DATE_LOCALE,
this.settingsForm.value.dateLocale
)
this.settings.set(
SETTINGS_KEYS.DATE_FORMAT,
this.settingsForm.value.dateFormat
)
this.settings.set(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_NEW_DOCUMENT,
this.settingsForm.value.notificationsConsumerNewDocument
)
this.settings.set(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS,
this.settingsForm.value.notificationsConsumerSuccess
)
this.settings.set(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_FAILED,
this.settingsForm.value.notificationsConsumerFailed
)
this.settings.set(
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD,
this.settingsForm.value.notificationsConsumerSuppressOnDashboard
)
this.settings.setLanguage(this.settingsForm.value.displayLanguage) this.settings.setLanguage(this.settingsForm.value.displayLanguage)
this.store.next(this.settingsForm.value) this.store.next(this.settingsForm.value)
this.documentListViewService.updatePageSize() this.documentListViewService.updatePageSize()
@ -149,14 +234,14 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
} }
get displayLanguageOptions(): LanguageOption[] { get displayLanguageOptions(): LanguageOption[] {
return [ return [{ code: '', name: $localize`Use system language` }].concat(
{code: "", name: $localize`Use system language`} this.settings.getLanguageOptions()
].concat(this.settings.getLanguageOptions()) )
} }
get dateLocaleOptions(): LanguageOption[] { get dateLocaleOptions(): LanguageOption[] {
return [ return [
{code: "", name: $localize`Use date format of display language`} { code: '', name: $localize`Use date format of display language` },
].concat(this.settings.getDateLocaleOptions()) ].concat(this.settings.getDateLocaleOptions())
} }
@ -170,18 +255,24 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
x.push(this.savedViewGroup.value[id]) x.push(this.savedViewGroup.value[id])
} }
if (x.length > 0) { if (x.length > 0) {
this.savedViewService.patchMany(x).subscribe(s => { this.savedViewService.patchMany(x).subscribe(
this.saveLocalSettings() (s) => {
}, error => { this.saveLocalSettings()
this.toastService.showError($localize`Error while storing settings on server: ${JSON.stringify(error.error)}`) },
}) (error) => {
this.toastService.showError(
$localize`Error while storing settings on server: ${JSON.stringify(
error.error
)}`
)
}
)
} else { } else {
this.saveLocalSettings() this.saveLocalSettings()
} }
} }
clearThemeColor() { clearThemeColor() {
this.settingsForm.get('themeColor').patchValue(''); this.settingsForm.get('themeColor').patchValue('')
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TagEditDialogComponent } from './tag-edit-dialog.component'; import { TagEditDialogComponent } from './tag-edit-dialog.component'
describe('TagEditDialogComponent', () => { describe('TagEditDialogComponent', () => {
let component: TagEditDialogComponent; let component: TagEditDialogComponent
let fixture: ComponentFixture<TagEditDialogComponent>; let fixture: ComponentFixture<TagEditDialogComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ TagEditDialogComponent ] declarations: [TagEditDialogComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(TagEditDialogComponent); fixture = TestBed.createComponent(TagEditDialogComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,20 +1,23 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'; import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
import { PaperlessTag } from 'src/app/data/paperless-tag'; import { PaperlessTag } from 'src/app/data/paperless-tag'
import { TagService } from 'src/app/services/rest/tag.service'; import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
import { randomColor } from 'src/app/utils/color'; import { randomColor } from 'src/app/utils/color'
@Component({ @Component({
selector: 'app-tag-edit-dialog', selector: 'app-tag-edit-dialog',
templateUrl: './tag-edit-dialog.component.html', templateUrl: './tag-edit-dialog.component.html',
styleUrls: ['./tag-edit-dialog.component.scss'] styleUrls: ['./tag-edit-dialog.component.scss'],
}) })
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> { export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
constructor(
constructor(service: TagService, activeModal: NgbActiveModal, toastService: ToastService) { service: TagService,
activeModal: NgbActiveModal,
toastService: ToastService
) {
super(service, activeModal, toastService) super(service, activeModal, toastService)
} }
@ -32,9 +35,8 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
color: new FormControl(randomColor()), color: new FormControl(randomColor()),
is_inbox_tag: new FormControl(false), is_inbox_tag: new FormControl(false),
matching_algorithm: new FormControl(1), matching_algorithm: new FormControl(1),
match: new FormControl(""), match: new FormControl(''),
is_insensitive: new FormControl(true) is_insensitive: new FormControl(true),
}) })
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TagListComponent } from './tag-list.component'; import { TagListComponent } from './tag-list.component'
describe('TagListComponent', () => { describe('TagListComponent', () => {
let component: TagListComponent; let component: TagListComponent
let fixture: ComponentFixture<TagListComponent>; let fixture: ComponentFixture<TagListComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ TagListComponent ] declarations: [TagListComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(TagListComponent); fixture = TestBed.createComponent(TagListComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,21 +1,22 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'; import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
import { PaperlessTag } from 'src/app/data/paperless-tag'; import { PaperlessTag } from 'src/app/data/paperless-tag'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'; import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { TagService } from 'src/app/services/rest/tag.service'; import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service'
import { GenericListComponent } from '../generic-list/generic-list.component'; import { GenericListComponent } from '../generic-list/generic-list.component'
import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'; import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.component'
@Component({ @Component({
selector: 'app-tag-list', selector: 'app-tag-list',
templateUrl: './tag-list.component.html', templateUrl: './tag-list.component.html',
styleUrls: ['./tag-list.component.scss'] styleUrls: ['./tag-list.component.scss'],
}) })
export class TagListComponent extends GenericListComponent<PaperlessTag> { export class TagListComponent extends GenericListComponent<PaperlessTag> {
constructor(
constructor(tagService: TagService, modalService: NgbModal, tagService: TagService,
modalService: NgbModal,
private list: DocumentListViewService, private list: DocumentListViewService,
toastService: ToastService toastService: ToastService
) { ) {
@ -27,7 +28,8 @@ export class TagListComponent extends GenericListComponent<PaperlessTag> {
} }
filterDocuments(object: PaperlessTag) { filterDocuments(object: PaperlessTag) {
this.list.quickFilter([{rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString()}]) this.list.quickFilter([
{ rule_type: FILTER_HAS_TAGS_ALL, value: object.id.toString() },
])
} }
} }

View File

@ -1,25 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NotFoundComponent } from './not-found.component'; import { NotFoundComponent } from './not-found.component'
describe('NotFoundComponent', () => { describe('NotFoundComponent', () => {
let component: NotFoundComponent; let component: NotFoundComponent
let fixture: ComponentFixture<NotFoundComponent>; let fixture: ComponentFixture<NotFoundComponent>
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ NotFoundComponent ] declarations: [NotFoundComponent],
}) }).compileComponents()
.compileComponents(); })
});
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(NotFoundComponent); fixture = TestBed.createComponent(NotFoundComponent)
component = fixture.componentInstance; component = fixture.componentInstance
fixture.detectChanges(); fixture.detectChanges()
}); })
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy()
}); })
}); })

View File

@ -1,15 +1,12 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core'
@Component({ @Component({
selector: 'app-not-found', selector: 'app-not-found',
templateUrl: './not-found.component.html', templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.scss'] styleUrls: ['./not-found.component.scss'],
}) })
export class NotFoundComponent implements OnInit { export class NotFoundComponent implements OnInit {
constructor() {}
constructor() { } ngOnInit(): void {}
ngOnInit(): void {
}
} }

View File

@ -27,40 +27,160 @@ export const FILTER_FULLTEXT_QUERY = 20
export const FILTER_FULLTEXT_MORELIKE = 21 export const FILTER_FULLTEXT_MORELIKE = 21
export const FILTER_RULE_TYPES: FilterRuleType[] = [ export const FILTER_RULE_TYPES: FilterRuleType[] = [
{
id: FILTER_TITLE,
filtervar: 'title__icontains',
datatype: 'string',
multi: false,
default: '',
},
{
id: FILTER_CONTENT,
filtervar: 'content__icontains',
datatype: 'string',
multi: false,
default: '',
},
{id: FILTER_TITLE, filtervar: "title__icontains", datatype: "string", multi: false, default: ""}, {
{id: FILTER_CONTENT, filtervar: "content__icontains", datatype: "string", multi: false, default: ""}, id: FILTER_ASN,
filtervar: 'archive_serial_number',
datatype: 'number',
multi: false,
},
{id: FILTER_ASN, filtervar: "archive_serial_number", datatype: "number", multi: false}, {
id: FILTER_CORRESPONDENT,
filtervar: 'correspondent__id',
isnull_filtervar: 'correspondent__isnull',
datatype: 'correspondent',
multi: false,
},
{
id: FILTER_DOCUMENT_TYPE,
filtervar: 'document_type__id',
isnull_filtervar: 'document_type__isnull',
datatype: 'document_type',
multi: false,
},
{id: FILTER_CORRESPONDENT, filtervar: "correspondent__id", isnull_filtervar: "correspondent__isnull", datatype: "correspondent", multi: false}, {
{id: FILTER_DOCUMENT_TYPE, filtervar: "document_type__id", isnull_filtervar: "document_type__isnull", datatype: "document_type", multi: false}, id: FILTER_IS_IN_INBOX,
filtervar: 'is_in_inbox',
datatype: 'boolean',
multi: false,
default: true,
},
{
id: FILTER_HAS_TAGS_ALL,
filtervar: 'tags__id__all',
datatype: 'tag',
multi: true,
},
{
id: FILTER_HAS_TAGS_ANY,
filtervar: 'tags__id__in',
datatype: 'tag',
multi: true,
},
{
id: FILTER_DOES_NOT_HAVE_TAG,
filtervar: 'tags__id__none',
datatype: 'tag',
multi: true,
},
{
id: FILTER_HAS_ANY_TAG,
filtervar: 'is_tagged',
datatype: 'boolean',
multi: false,
default: true,
},
{id: FILTER_IS_IN_INBOX, filtervar: "is_in_inbox", datatype: "boolean", multi: false, default: true}, {
{id: FILTER_HAS_TAGS_ALL, filtervar: "tags__id__all", datatype: "tag", multi: true}, id: FILTER_CREATED_BEFORE,
{id: FILTER_HAS_TAGS_ANY, filtervar: "tags__id__in", datatype: "tag", multi: true}, filtervar: 'created__date__lt',
{id: FILTER_DOES_NOT_HAVE_TAG, filtervar: "tags__id__none", datatype: "tag", multi: true}, datatype: 'date',
{id: FILTER_HAS_ANY_TAG, filtervar: "is_tagged", datatype: "boolean", multi: false, default: true}, multi: false,
},
{
id: FILTER_CREATED_AFTER,
filtervar: 'created__date__gt',
datatype: 'date',
multi: false,
},
{id: FILTER_CREATED_BEFORE, filtervar: "created__date__lt", datatype: "date", multi: false}, {
{id: FILTER_CREATED_AFTER, filtervar: "created__date__gt", datatype: "date", multi: false}, id: FILTER_CREATED_YEAR,
filtervar: 'created__year',
datatype: 'number',
multi: false,
},
{
id: FILTER_CREATED_MONTH,
filtervar: 'created__month',
datatype: 'number',
multi: false,
},
{
id: FILTER_CREATED_DAY,
filtervar: 'created__day',
datatype: 'number',
multi: false,
},
{id: FILTER_CREATED_YEAR, filtervar: "created__year", datatype: "number", multi: false}, {
{id: FILTER_CREATED_MONTH, filtervar: "created__month", datatype: "number", multi: false}, id: FILTER_ADDED_BEFORE,
{id: FILTER_CREATED_DAY, filtervar: "created__day", datatype: "number", multi: false}, filtervar: 'added__date__lt',
datatype: 'date',
multi: false,
},
{
id: FILTER_ADDED_AFTER,
filtervar: 'added__date__gt',
datatype: 'date',
multi: false,
},
{id: FILTER_ADDED_BEFORE, filtervar: "added__date__lt", datatype: "date", multi: false}, {
{id: FILTER_ADDED_AFTER, filtervar: "added__date__gt", datatype: "date", multi: false}, id: FILTER_MODIFIED_BEFORE,
filtervar: 'modified__date__lt',
datatype: 'date',
multi: false,
},
{
id: FILTER_MODIFIED_AFTER,
filtervar: 'modified__date__gt',
datatype: 'date',
multi: false,
},
{
id: FILTER_ASN_ISNULL,
filtervar: 'archive_serial_number__isnull',
datatype: 'boolean',
multi: false,
},
{id: FILTER_MODIFIED_BEFORE, filtervar: "modified__date__lt", datatype: "date", multi: false}, {
{id: FILTER_MODIFIED_AFTER, filtervar: "modified__date__gt", datatype: "date", multi: false}, id: FILTER_TITLE_CONTENT,
{id: FILTER_ASN_ISNULL, filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false}, filtervar: 'title_content',
datatype: 'string',
multi: false,
},
{id: FILTER_TITLE_CONTENT, filtervar: "title_content", datatype: "string", multi: false}, {
id: FILTER_FULLTEXT_QUERY,
filtervar: 'query',
datatype: 'string',
multi: false,
},
{id: FILTER_FULLTEXT_QUERY, filtervar: "query", datatype: "string", multi: false}, {
id: FILTER_FULLTEXT_MORELIKE,
{id: FILTER_FULLTEXT_MORELIKE, filtervar: "more_like_id", datatype: "number", multi: false}, filtervar: 'more_like_id',
datatype: 'number',
multi: false,
},
] ]
export interface FilterRuleType { export interface FilterRuleType {

View File

@ -1,10 +1,13 @@
import { FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY } from "./filter-rule-type" import {
FILTER_FULLTEXT_MORELIKE,
FILTER_FULLTEXT_QUERY,
} from './filter-rule-type'
export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] { export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
if (filterRules) { if (filterRules) {
let newRules: FilterRule[] = [] let newRules: FilterRule[] = []
for (let rule of filterRules) { for (let rule of filterRules) {
newRules.push({rule_type: rule.rule_type, value: rule.value}) newRules.push({ rule_type: rule.rule_type, value: rule.value })
} }
return newRules return newRules
} else { } else {
@ -13,7 +16,13 @@ export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
} }
export function isFullTextFilterRule(filterRules: FilterRule[]): boolean { export function isFullTextFilterRule(filterRules: FilterRule[]): boolean {
return filterRules.find(r => r.rule_type == FILTER_FULLTEXT_QUERY || r.rule_type == FILTER_FULLTEXT_MORELIKE) != null return (
filterRules.find(
(r) =>
r.rule_type == FILTER_FULLTEXT_QUERY ||
r.rule_type == FILTER_FULLTEXT_MORELIKE
) != null
)
} }
export interface FilterRule { export interface FilterRule {

View File

@ -1,5 +1,4 @@
import { ObjectWithId } from './object-with-id'; import { ObjectWithId } from './object-with-id'
export const MATCH_ANY = 1 export const MATCH_ANY = 1
export const MATCH_ALL = 2 export const MATCH_ALL = 2
@ -9,26 +8,48 @@ export const MATCH_FUZZY = 5
export const MATCH_AUTO = 6 export const MATCH_AUTO = 6
export const MATCHING_ALGORITHMS = [ export const MATCHING_ALGORITHMS = [
{id: MATCH_ANY, shortName: $localize`Any word`, name: $localize`Any: Document contains any of these words (space separated)`}, {
{id: MATCH_ALL, shortName: $localize`All words`, name: $localize`All: Document contains all of these words (space separated)`}, id: MATCH_ANY,
{id: MATCH_LITERAL, shortName: $localize`Exact match`, name: $localize`Exact: Document contains this string`}, shortName: $localize`Any word`,
{id: MATCH_REGEX, shortName: $localize`Regular expression`, name: $localize`Regular expression: Document matches this regular expression`}, name: $localize`Any: Document contains any of these words (space separated)`,
{id: MATCH_FUZZY, shortName: $localize`Fuzzy word`, name: $localize`Fuzzy: Document contains a word similar to this word`}, },
{id: MATCH_AUTO, shortName: $localize`Automatic`, name: $localize`Auto: Learn matching automatically`}, {
id: MATCH_ALL,
shortName: $localize`All words`,
name: $localize`All: Document contains all of these words (space separated)`,
},
{
id: MATCH_LITERAL,
shortName: $localize`Exact match`,
name: $localize`Exact: Document contains this string`,
},
{
id: MATCH_REGEX,
shortName: $localize`Regular expression`,
name: $localize`Regular expression: Document matches this regular expression`,
},
{
id: MATCH_FUZZY,
shortName: $localize`Fuzzy word`,
name: $localize`Fuzzy: Document contains a word similar to this word`,
},
{
id: MATCH_AUTO,
shortName: $localize`Automatic`,
name: $localize`Auto: Learn matching automatically`,
},
] ]
export interface MatchingModel extends ObjectWithId { export interface MatchingModel extends ObjectWithId {
name?: string
name?: string slug?: string
slug?: string match?: string
match?: string matching_algorithm?: number
matching_algorithm?: number is_insensitive?: boolean
is_insensitive?: boolean
document_count?: number
document_count?: number
} }

Some files were not shown because too many files have changed in this diff Show More