Compare commits

...

89 Commits

Author SHA1 Message Date
shamoon
af7cd8fadf Merge branch 'dev' into feature-10188-savedview-count 2025-06-27 15:16:52 -07:00
shamoon
36473838ff Coverage 2025-06-27 15:16:09 -07:00
shamoon
a9085c65c5 Chore: fix some test warnings / errors 2025-06-27 15:03:10 -07:00
shamoon
e312425b1c Fix: reset search query for preview on reset filter (#10279) 2025-06-27 14:36:38 -07:00
shamoon
d6d419aeb0 Update app-frame.component.ts 2025-06-27 14:14:24 -07:00
shamoon
9d18382f12 Merge branch 'dev' into feature-10188-savedview-count 2025-06-27 14:13:51 -07:00
GitHub Actions
13fe064f6e Auto translate strings 2025-06-27 21:08:27 +00:00
shamoon
958f98d7e5 Chore: update to Angular 20 (#10273) 2025-06-27 14:06:40 -07:00
shamoon
dfad3c4d8e Chore: clarify file deletion logging 2025-06-27 13:34:44 -07:00
shamoon
2db4c4c698 Add setting, trigger global refresh when needed 2025-06-22 20:07:43 -07:00
shamoon
88c6be5940 Include count badge on dashboard widgets 2025-06-22 20:07:43 -07:00
shamoon
fa1c01a732 Enhancement: show document count on sidebar saved views 2025-06-22 20:07:42 -07:00
shamoon
37267f3f04 Chore: add webpack directly 2025-06-22 20:03:48 -07:00
shamoon
b34538d991 Update support.yml 2025-06-22 08:03:41 -07:00
shamoon
fc97bd1315 Chore: create support discussion template 2025-06-22 08:02:26 -07:00
shamoon
dbf3721ec2 Chore: reject absurd max age values (#10243) 2025-06-22 07:39:36 -07:00
shamoon
59afbe09b1 Chore: remove PAPERLESS_DEBUG references to avoid confusion 2025-06-20 20:46:11 -07:00
shamoon
bfeaa1b119 Chore: update settings to pathlib 2025-06-20 10:50:44 -07:00
github-actions[bot]
e1c3124698 Changelog v2.17.1 - GHA (#10229) 2025-06-19 12:40:37 -07:00
shamoon
f2e22e103b Bump version to 2.17.1 2025-06-19 12:07:49 -07:00
github-actions[bot]
dda94f013e New Crowdin translations by GitHub Action (#10228)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-19 12:06:52 -07:00
GitHub Actions
caf00e7ead Auto translate strings 2025-06-19 18:52:01 +00:00
shamoon
f214440d2e Fix: correct PAPERLESS_EMPTY_TRASH_DIR to Path (#10227) 2025-06-19 11:50:23 -07:00
github-actions[bot]
240c9ac511 Changelog v2.17.0 - GHA (#10225) 2025-06-19 10:43:13 -07:00
shamoon
a2c9bc346a Bump version to 2.17.0 2025-06-19 10:12:41 -07:00
github-actions[bot]
b1cbb1c73a New Crowdin translations by GitHub Action (#10131)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-19 09:32:51 -07:00
shamoon
434b1e3245 Fix: replace strtobool 2025-06-19 09:02:10 -07:00
GitHub Actions
497fdcaf4e Auto translate strings 2025-06-19 15:31:10 +00:00
shamoon
52b95f2b62 QoL: log version at startup, show backend vs frontend mismatch in system status (#10214) 2025-06-19 08:29:34 -07:00
shamoon
83391af866 Fix: more api fixes (#10204) 2025-06-19 08:28:41 -07:00
GitHub Actions
0a1786f39b Auto translate strings 2025-06-19 14:50:29 +00:00
shamoon
3b069ac034 Fix: restore expected pre-2.16 scheduled workflow offset behavior (#10218) 2025-06-19 14:47:54 +00:00
Sebastian Steinbeißer
07882b918b Chore: switch from os.path to pathlib.Path (#9933)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-06-18 17:16:59 +00:00
shamoon
cc5ba71f06 Chore: remove spaces from run log 2025-06-17 16:02:45 -07:00
GitHub Actions
f16b8fbe2a Auto translate strings 2025-06-17 19:02:55 +00:00
shamoon
fe54d99356 Chore: bump angular to 19.12.14 (#10212) 2025-06-17 12:01:15 -07:00
GitHub Actions
a49efb07ea Auto translate strings 2025-06-17 05:46:29 +00:00
shamoon
e4fd008441 Fix: fix some API crashes (#10196) 2025-06-16 22:44:39 -07:00
robertmx
de12023311 Fix: remove duplicate base path in websocket urls (#10194) 2025-06-16 10:50:15 -07:00
shamoon
60ebdc0ad6 Fix: use hard delete for custom fields with workflow removal (#10191) 2025-06-16 07:09:22 -07:00
Michael Hobl
cbd9823ad6 Documentation: correct Gotenberg API timeout value (#10186) 2025-06-15 07:36:49 -07:00
shamoon
ce76303a32 Feature: add Persian translation (#10183) 2025-06-14 19:14:51 -07:00
Kilian
246f17c6c8 Enhancement: support import of zipped export (#10073) 2025-06-13 10:06:37 -07:00
shamoon
4313635b01 Revert "Development: fix dependabot by specifying pnpm version (#10103)"
This reverts commit 9c9a0e4496.
2025-06-10 09:19:00 -07:00
shamoon
7ca2bd0666 Fix: fix mail account test api schema (#10164) 2025-06-10 07:09:24 -07:00
shamoon
4c6075e962 Fix: correct api schema for mail_account process (#10157) 2025-06-09 14:51:58 -07:00
shamoon
8d48e99487 Fix: correct api schema for next_asn (#10151) 2025-06-09 09:32:34 -07:00
shamoon
454a2d9e9e Fix: fix email and notes endpoints api spec (#10148) 2025-06-09 07:09:28 -07:00
shamoon
b8c713d4b9 Chore: get CI running on crowdin PRs 2025-06-06 00:29:06 -07:00
github-actions[bot]
2a8cb87232 Changelog v2.16.3 - GHA (#10129)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-05 14:41:33 -07:00
shamoon
e5673e4817 Bump version to 2.16.3 2025-06-05 13:26:20 -07:00
shamoon
ed48392450 Merge branch 'dev' 2025-06-05 13:25:32 -07:00
github-actions[bot]
48d6bedf1c New Crowdin translations by GitHub Action (#10127) 2025-06-05 13:09:27 -07:00
github-actions[bot]
ec2dc6cd80 New Crowdin translations by GitHub Action (#10032) 2025-06-05 12:47:57 -07:00
shamoon
51e6eed72a Fix: handle whoosh query correction errors (#10121) 2025-06-05 08:57:25 -07:00
shamoon
422bffe1a6 Performance: pre-filter document list in scheduled workflow checks (#10031) 2025-06-03 21:47:29 +00:00
dependabot[bot]
31351c5f5c Chore(deps): Bump the small-changes group across 1 directory with 3 updates (#10085)
Bumps the small-changes group with 3 updates in the / directory: [concurrent-log-handler](https://github.com/Preston-Landers/concurrent-log-handler), [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF) and [setproctitle](https://github.com/dvarrazzo/py-setproctitle).


Updates `concurrent-log-handler` from 0.9.25 to 0.9.26
- [Release notes](https://github.com/Preston-Landers/concurrent-log-handler/releases)
- [Changelog](https://github.com/Preston-Landers/concurrent-log-handler/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Preston-Landers/concurrent-log-handler/compare/0.9.25...0.9.26)

Updates `ocrmypdf` from 16.10.0 to 16.10.2
- [Release notes](https://github.com/ocrmypdf/OCRmyPDF/releases)
- [Changelog](https://github.com/ocrmypdf/OCRmyPDF/blob/main/docs/release_notes.md)
- [Commits](https://github.com/ocrmypdf/OCRmyPDF/compare/v16.10.0...v16.10.2)

Updates `setproctitle` from 1.3.5 to 1.3.6
- [Changelog](https://github.com/dvarrazzo/py-setproctitle/blob/master/HISTORY.rst)
- [Commits](https://github.com/dvarrazzo/py-setproctitle/compare/version-1.3.5...version-1.3.6)

---
updated-dependencies:
- dependency-name: concurrent-log-handler
  dependency-version: 0.9.26
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: ocrmypdf
  dependency-version: 16.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: setproctitle
  dependency-version: 1.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 19:57:32 +00:00
shamoon
c30cf2e0cd Chore: fix naive datetime warnings 2025-06-03 12:42:18 -07:00
shamoon
e97cfb9b5e Chore: refactor consumer plugin checks to a pre-flight plugin (#9994) 2025-06-03 19:28:49 +00:00
dependabot[bot]
42100588d5 Chore(deps): Update granian[uvloop] requirement from ~=2.2.0 to ~=2.3.2 (#10055)
Updates the requirements on [granian[uvloop]](https://github.com/emmett-framework/granian) to permit the latest version.
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.2.0...v2.3.2)

---
updated-dependencies:
- dependency-name: granian[uvloop]
  dependency-version: 2.3.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-03 12:15:00 -07:00
shamoon
bc2facc87f Chore: use pathlib in remaining tests 2025-06-03 11:48:17 -07:00
shamoon
b4c6c4b61e Documentation: clarify db behavior (#10113) 2025-06-03 09:41:29 -07:00
shamoon
4e082f997c Fix: better handle favicon with static dir (#10107) 2025-06-03 08:05:59 -07:00
GitHub Actions
1512599f4f Auto translate strings 2025-06-02 19:12:59 +00:00
dependabot[bot]
6c8f0b54ad Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 18 updates (#10099)
* Chore(deps): Bump the frontend-angular-dependencies group

Bumps the frontend-angular-dependencies group in /src-ui with 22 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `19.2.14` | `20.0.1` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `19.2.9` | `19.2.14` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `19.2.9` | `19.2.14` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `19.2.9` | `19.2.14` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `19.2.9` | `19.2.14` |
| [@angular/localize](https://github.com/angular/angular) | `19.2.9` | `19.2.14` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `19.2.9` | `19.2.14` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `19.2.9` | `19.2.14` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `19.2.9` | `19.2.14` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `14.7.0` | `14.9.0` |
| [ngx-cookie-service](https://github.com/stevermeister/ngx-cookie-service) | `19.1.2` | `20.0.1` |
| [ngx-device-detector](https://github.com/AhsanAyaz/ngx-device-detector) | `9.0.0` | `10.0.2` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `19.2.10` | `19.2.14` |
| [@angular-devkit/core](https://github.com/angular/angular-cli) | `19.2.10` | `19.2.14` |
| [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `19.2.10` | `19.2.14` |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `19.3.0` | `19.6.0` |
| [@angular-eslint/eslint-plugin](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin) | `19.3.0` | `19.6.0` |
| [@angular-eslint/eslint-plugin-template](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template) | `19.3.0` | `19.6.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `19.3.0` | `19.6.0` |
| [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) | `19.3.0` | `19.6.0` |
| [@angular/cli](https://github.com/angular/angular-cli) | `19.2.10` | `19.2.14` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `19.2.9` | `19.2.14` |


Updates `@angular/cdk` from 19.2.14 to 20.0.1
- [Release notes](https://github.com/angular/components/releases)
- [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/components/compare/19.2.14...20.0.1)

Updates `@angular/common` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/common)

Updates `@angular/compiler` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/compiler)

Updates `@angular/core` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/core)

Updates `@angular/forms` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/forms)

Updates `@angular/localize` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/compare/19.2.9...19.2.14)

Updates `@angular/platform-browser` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/platform-browser-dynamic)

Updates `@angular/router` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/router)

Updates `@ng-select/ng-select` from 14.7.0 to 14.9.0
- [Release notes](https://github.com/ng-select/ng-select/releases)
- [Changelog](https://github.com/ng-select/ng-select/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ng-select/ng-select/compare/v14.7.0...v14.9.0)

Updates `ngx-cookie-service` from 19.1.2 to 20.0.1
- [Release notes](https://github.com/stevermeister/ngx-cookie-service/releases)
- [Changelog](https://github.com/stevermeister/ngx-cookie-service/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stevermeister/ngx-cookie-service/compare/v19.1.2...v20.0.1)

Updates `ngx-device-detector` from 9.0.0 to 10.0.2
- [Release notes](https://github.com/AhsanAyaz/ngx-device-detector/releases)
- [Changelog](https://github.com/AhsanAyaz/ngx-device-detector/blob/master/steps-to-release.md)
- [Commits](https://github.com/AhsanAyaz/ngx-device-detector/compare/v9.0.0...v10.0.2)

Updates `@angular-devkit/build-angular` from 19.2.10 to 19.2.14
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/19.2.10...19.2.14)

Updates `@angular-devkit/core` from 19.2.10 to 19.2.14
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/19.2.10...19.2.14)

Updates `@angular-devkit/schematics` from 19.2.10 to 19.2.14
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/19.2.10...19.2.14)

Updates `@angular-eslint/builder` from 19.3.0 to 19.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/builder/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v19.6.0/packages/builder)

Updates `@angular-eslint/eslint-plugin` from 19.3.0 to 19.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v19.6.0/packages/eslint-plugin)

Updates `@angular-eslint/eslint-plugin-template` from 19.3.0 to 19.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v19.6.0/packages/eslint-plugin-template)

Updates `@angular-eslint/schematics` from 19.3.0 to 19.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/schematics/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v19.6.0/packages/schematics)

Updates `@angular-eslint/template-parser` from 19.3.0 to 19.6.0
- [Release notes](https://github.com/angular-eslint/angular-eslint/releases)
- [Changelog](https://github.com/angular-eslint/angular-eslint/blob/main/packages/template-parser/CHANGELOG.md)
- [Commits](https://github.com/angular-eslint/angular-eslint/commits/v19.6.0/packages/template-parser)

Updates `@angular/cli` from 19.2.10 to 19.2.14
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/19.2.10...19.2.14)

Updates `@angular/compiler-cli` from 19.2.9 to 19.2.14
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.14/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-version: 20.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@ng-select/ng-select"
  dependency-version: 14.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: ngx-cookie-service
  dependency-version: 20.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: frontend-angular-dependencies
- dependency-name: ngx-device-detector
  dependency-version: 10.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 19.2.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/core"
  dependency-version: 19.2.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/schematics"
  dependency-version: 19.2.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/builder"
  dependency-version: 19.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/eslint-plugin"
  dependency-version: 19.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/eslint-plugin-template"
  dependency-version: 19.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/schematics"
  dependency-version: 19.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/template-parser"
  dependency-version: 19.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 19.2.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 19.2.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* Revert "Chore(deps): Bump the frontend-angular-dependencies group"

This reverts commit 9bd02e2bc9.

* Bump core angular cli to 19.2.14

* Bump @angular-devkit/build-angular to 19.2.14

* Bump @angular-devkit/core to 19.2.14

* bump @angular-devkit/schematics@ to 19.2.14

* bump @angular-eslint packages to 19.7.0

* Bump @ng-select/ng-select to 14.9.0

* Upgrade angular core and compiler to 19.2.14

* pnpm up @angular/forms@~19.2.14

* pnpm up @angular/localize@~19.2.14 --lockfile-only

* pnpm up @angular/platform-browser@~19.2.14 --lockfile-only

* pnpm up @angular/platform-browser-dynamic@~19.2.14 --lockfile-only

* pnpm up @angular/router@~19.2.14 --lockfile-only

* pnpm up @angular/compiler-cli@~19.2.14 --lockfile-only

* @angular/common to 19.2.13

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-06-02 12:11:17 -07:00
dependabot[bot]
419ee9d6e7 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#10100)
Bumps the frontend-eslint-dependencies group in /src-ui with 4 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils) and [eslint](https://github.com/eslint/eslint).


Updates `@typescript-eslint/eslint-plugin` from 8.31.1 to 8.33.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.31.1 to 8.33.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.0/packages/parser)

Updates `@typescript-eslint/utils` from 8.31.1 to 8.33.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.33.0/packages/utils)

Updates `eslint` from 9.25.1 to 9.28.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.25.1...v9.28.0)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/utils"
  dependency-version: 8.33.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  dependency-version: 9.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 16:43:57 +00:00
dependabot[bot]
34b649aa01 Chore(deps): Bump bootstrap from 5.3.3 to 5.3.6 in /src-ui (#10091)
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.3 to 5.3.6.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.3...v5.3.6)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-version: 5.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 16:32:53 +00:00
dependabot[bot]
a9982abde8 Chore(deps-dev): Bump @codecov/webpack-plugin in /src-ui (#10090)
Bumps @codecov/webpack-plugin from 1.9.0 to 1.9.1.

---
updated-dependencies:
- dependency-name: "@codecov/webpack-plugin"
  dependency-version: 1.9.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 16:18:22 +00:00
dependabot[bot]
a94cc62207 Chore(deps-dev): Bump @types/node from 22.15.3 to 22.15.29 in /src-ui (#10089)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.15.3 to 22.15.29.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.15.29
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-02 16:07:50 +00:00
GitHub Actions
fc1b3e674b Auto translate strings 2025-06-02 15:52:11 +00:00
dependabot[bot]
603ad6c817 Chore(deps): Bump zone.js from 0.15.0 to 0.15.1 in /src-ui (#10088) 2025-06-02 15:50:25 +00:00
shamoon
9c9a0e4496 Development: fix dependabot by specifying pnpm version (#10103) 2025-06-02 08:28:14 -07:00
sidey79
7c33785c07 Development: devcontainer setup, docs and enable dependabot (#10081)
* fix: container setup and task description

* feat: enable dependabot for devcontainer

* fix: dont install latest uv and dont install uvx

* Cleanup decontainer readme

* Fix the reset venv command

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-06-01 17:59:46 +00:00
shamoon
9c32d931bc Dont label dependabot as enhancments 2025-05-31 17:02:07 -07:00
dependabot[bot]
d7b2e002ce docker(deps): bump astral-sh/uv (#10084)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.7.8-python3.12-bookworm-slim to 0.7.9-python3.12-bookworm-slim.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.7.8...0.7.9)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.7.9-python3.12-bookworm-slim
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-31 09:27:59 -07:00
Trenton H
51e70f0a20 Removes the reviewers field, excludes Django and related from the small changes group (#10076) 2025-05-31 15:35:51 +00:00
mamasch19
bd257925bd Documentation: fix typo in configuration.md (#10077) 2025-05-29 15:18:30 -07:00
shamoon
15b1b83c66 Chore/fix: cleanup user or group references in settings upon deletion (#10049) 2025-05-29 19:05:48 +00:00
dependabot[bot]
b06c0a0eba docker(deps): Bump astral-sh/uv from 0.6.16-python3.12-bookworm-slim to 0.7.8-python3.12-bookworm-slim (#10056)
* docker(deps): Bump astral-sh/uv

Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.6.16-python3.12-bookworm-slim to 0.7.8-python3.12-bookworm-slim.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.6.16...0.7.8)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.7.8-python3.12-bookworm-slim
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Upgrades the CI workflow to also use 0.7.x

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2025-05-29 11:17:15 -07:00
matthesrieke
e9746aa0e3 Enhancement: include DOCUMENT_TYPE to post consume scripts (#9977)
* expose DOCUMENT_TYPE to post consume scripts

* Apply suggestions from code review

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-05-28 23:32:59 +00:00
Freilichtbühne
bfaab21589 Fix: Add exception to utime in copy_basic_file_stats (#10070) 2025-05-28 15:13:03 -07:00
GitHub Actions
3849569bd1 Auto translate strings 2025-05-27 23:01:05 +00:00
shamoon
c40a7751b9 Fix: include base href when opening global search result in new window (#10066) 2025-05-27 15:59:17 -07:00
shamoon
f39463ff4e Add a more helpful docstring to schedule logic, scheduled test 2025-05-27 13:05:42 -07:00
shamoon
2ada8ec681 Chore: silence migration result if no docs 2025-05-26 10:31:37 -07:00
shamoon
bdbf1b57ce Documentation: add workflow permissions note (#10038) 2025-05-25 09:18:48 -07:00
github-actions[bot]
4c6fdbb21f Documentation: Add v2.16.2 changelog (#10029)
* Changelog v2.16.2 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-05-24 11:57:09 -07:00
shamoon
889c4378a9 Merge branch 'dev' 2025-05-24 11:55:16 -07:00
shamoon
06dd039083 Revert "Chore: remove invalid branches-ignores"
This reverts commit 28a1b9d1ac.
2025-05-24 11:46:47 -07:00
308 changed files with 28125 additions and 13118 deletions

View File

@@ -83,7 +83,8 @@ RUN set -eux \
&& apt-get update \ && apt-get update \
&& apt-get install --yes --quiet ${PYTHON_PACKAGES} && apt-get install --yes --quiet ${PYTHON_PACKAGES}
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv COPY --from=ghcr.io/astral-sh/uv:0.7.8 /uv /bin/uv
RUN set -eux \ RUN set -eux \
&& echo "Installing pre-built updates" \ && echo "Installing pre-built updates" \
@@ -128,7 +129,6 @@ RUN set -eux \
&& echo "Configuring ImageMagick" \ && echo "Configuring ImageMagick" \
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml && mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
COPY --from=ghcr.io/astral-sh/uv:0.6 /uv /bin/uv
# Packages needed only for building a few quick Python # Packages needed only for building a few quick Python
# dependencies # dependencies

View File

@@ -47,39 +47,19 @@ To start the DevContainer:
1. Open VSCode. 1. Open VSCode.
2. Open the project folder. 2. Open the project folder.
3. Open the command palette: 3. Open the command palette and choose `Dev Containers: Rebuild and Reopen in Container`.
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
4. Type and select `Dev Containers: Rebuild and Reopen in Container`.
VSCode will build and start the DevContainer environment. VSCode will build and start the DevContainer environment.
### Step 2: Initial Setup ### Step 2: Initial Setup
Once the DevContainer is up and running, perform the following steps: Once the DevContainer is up and running, run the `Project Setup: Run all Init Tasks` task to initialize the project.
1. **Compile Frontend Assets**: Alternatively, the Project Setup can be done with individual tasks:
- Open the command palette: 1. **Compile Frontend Assets**: `Maintenance: Compile frontend for production`.
- **Windows/Linux**: `Ctrl+Shift+P` 2. **Run Database Migrations**: `Maintenance: manage.py migrate`.
- **Mac**: `Cmd+Shift+P` 3. **Create Superuser**: `Maintenance: manage.py createsuperuser`.
- Select `Tasks: Run Task`.
- Choose `Frontend Compile`.
2. **Run Database Migrations**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Migrate Database`.
3. **Create Superuser**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Create Superuser`.
### Debugging and Running Services ### Debugging and Running Services
@@ -95,11 +75,8 @@ You can start and debug backend services either as debugging sessions via `launc
#### Using Tasks #### Using Tasks
1. Open the command palette: 1. Open the command palette and select `Tasks: Run Task`.
- **Windows/Linux**: `Ctrl+Shift+P` 2. Choose the desired task:
- **Mac**: `Cmd+Shift+P`
2. Select `Tasks: Run Task`.
3. Choose the desired task:
- `Runserver` - `Runserver`
- `Document Consumer` - `Document Consumer`
- `Celery` - `Celery`

View File

@@ -156,7 +156,7 @@
"label": "Maintenance: recreate .venv", "label": "Maintenance: recreate .venv",
"description": "Recreate the python virtual environment and install python dependencies", "description": "Recreate the python virtual environment and install python dependencies",
"type": "shell", "type": "shell",
"command": "rm -R -v .venv/* || uv install --dev", "command": "rm -rf .venv && uv venv && uv sync --dev",
"group": "none", "group": "none",
"presentation": { "presentation": {
"echo": true, "echo": true,

55
.github/DISCUSSION_TEMPLATE/support.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
title: "[Support] "
body:
- type: textarea
id: description
attributes:
label: What's your question or issue?
description: Provide a clear and concise description of what you're trying to do, and what's going wrong.
placeholder: |
I'm trying to...
[Include screenshots if helpful]
validations:
required: true
- type: textarea
id: steps
attributes:
label: What have you tried?
description: Describe any steps you've already taken to troubleshoot or solve the issue.
placeholder: |
- I checked the logs and saw...
- I followed the install guide and tried...
- type: input
id: version
attributes:
label: Paperless-ngx version
placeholder: e.g. 1.14.0
validations:
required: true
- type: input
id: host-os
attributes:
label: Host OS
description: Include architecture if relevant.
placeholder: e.g. Ubuntu 22.04 / Raspberry Pi arm64
- type: dropdown
id: install-method
attributes:
label: Installation method
options:
- Docker - official image
- Docker - linuxserver.io image
- Bare metal
- Other (please describe above)
- type: textarea
id: system-status
attributes:
label: System status
description: If available, copy & paste the system status output from Settings > System Status > Copy
render: json
- type: textarea
id: logs
attributes:
label: Relevant logs or output
description: If you have logs, errors that might help, paste it here.
render: bash

View File

@@ -16,9 +16,6 @@ updates:
labels: labels:
- "frontend" - "frontend"
- "dependencies" - "dependencies"
# Add reviewers
reviewers:
- "paperless-ngx/frontend"
groups: groups:
frontend-angular-dependencies: frontend-angular-dependencies:
patterns: patterns:
@@ -44,9 +41,6 @@ updates:
labels: labels:
- "backend" - "backend"
- "dependencies" - "dependencies"
# Add reviewers
reviewers:
- "paperless-ngx/backend"
groups: groups:
development: development:
patterns: patterns:
@@ -65,6 +59,9 @@ updates:
update-types: update-types:
- "minor" - "minor"
- "patch" - "patch"
exclude-patterns:
- "*django*"
- "drf-*"
pre-built: pre-built:
patterns: patterns:
- psycopg* - psycopg*
@@ -79,9 +76,6 @@ updates:
labels: labels:
- "ci-cd" - "ci-cd"
- "dependencies" - "dependencies"
# Add reviewers
reviewers:
- "paperless-ngx/ci-cd"
groups: groups:
actions: actions:
update-types: update-types:
@@ -90,12 +84,12 @@ updates:
- "patch" - "patch"
# Update Dockerfile in root directory # Update Dockerfile in root directory
- package-ecosystem: "docker" - package-ecosystem: "docker"
directory: "/" directories:
- "/"
- "/.devcontainer/"
schedule: schedule:
interval: "weekly" interval: "weekly"
open-pull-requests-limit: 5 open-pull-requests-limit: 5
reviewers:
- "paperless-ngx/ci-cd"
labels: labels:
- "dependencies" - "dependencies"
commit-message: commit-message:
@@ -107,8 +101,6 @@ updates:
schedule: schedule:
interval: "weekly" interval: "weekly"
open-pull-requests-limit: 5 open-pull-requests-limit: 5
reviewers:
- "paperless-ngx/ci-cd"
labels: labels:
- "dependencies" - "dependencies"
commit-message: commit-message:

View File

@@ -6,9 +6,13 @@ on:
- 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+'
# https://semver.org/#spec-item-9 # https://semver.org/#spec-item-9
- 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-beta.rc[0-9]+'
branches-ignore:
- 'translations**'
pull_request: pull_request:
branches-ignore:
- 'translations**'
env: env:
DEFAULT_UV_VERSION: "0.6.x" DEFAULT_UV_VERSION: "0.7.x"
# This is the default version of Python to use in most steps which aren't specific # This is the default version of Python to use in most steps which aren't specific
DEFAULT_PYTHON_VERSION: "3.11" DEFAULT_PYTHON_VERSION: "3.11"
jobs: jobs:

View File

@@ -14,6 +14,8 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
token: ${{ secrets.PNGX_BOT_PAT }}
- name: crowdin action - name: crowdin action
uses: crowdin/github-action@v2 uses: crowdin/github-action@v2
with: with:

View File

@@ -37,7 +37,7 @@ jobs:
labels.push('bug'); labels.push('bug');
} else if (/^feature/i.test(title)) { } else if (/^feature/i.test(title)) {
labels.push('enhancement'); labels.push('enhancement');
} else { } else if (!/^(dependabot)/i.test(title)) {
labels.push('enhancement'); // Default fallback labels.push('enhancement'); // Default fallback
} }

View File

@@ -21,7 +21,7 @@ ARG PNGX_TAG_VERSION=
RUN set -eux && \ RUN set -eux && \
case "${PNGX_TAG_VERSION}" in \ case "${PNGX_TAG_VERSION}" in \
dev|beta|fix*|feature*) \ dev|beta|fix*|feature*) \
sed -i -E "s/version: '([0-9\.]+)'/version: '\1 #${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \ sed -i -E "s/tag: '([a-z\.]+)'/tag: '${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \
;; \ ;; \
esac esac
@@ -32,7 +32,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs # Purpose: Installs s6-overlay and rootfs
# Comments: # Comments:
# - Don't leave anything extra in here either # - Don't leave anything extra in here either
FROM ghcr.io/astral-sh/uv:0.6.16-python3.12-bookworm-slim AS s6-overlay-base FROM ghcr.io/astral-sh/uv:0.7.9-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6 WORKDIR /usr/src/s6

View File

@@ -13,8 +13,8 @@ echo $(date +%s) > /var/run/s6/container_environment/PAPERLESS_START_TIME_S
# Check if we're starting as a non-root user # Check if we're starting as a non-root user
if [ "$(id --user)" != "0" ]; then if [ "$(id --user)" != "0" ]; then
printf "true" > /var/run/s6/container_environment/USER_IS_NON_ROOT printf "true" > /var/run/s6/container_environment/USER_IS_NON_ROOT
echo "${log_prefix} paperless-ngx docker container running under a user ($(id --user):$(id --group))" echo "${log_prefix} paperless-ngx docker container running under a user ($(id --user):$(id --group))"
else else
printf "/usr/src/paperless" > /var/run/s6/container_environment/HOME printf "/usr/src/paperless" > /var/run/s6/container_environment/HOME
echo "${log_prefix} paperless-ngx docker container starting init as root" echo "${log_prefix} paperless-ngx docker container starting init as root"
fi fi

View File

@@ -333,7 +333,7 @@ must be provided to import. If this value is lost, the export cannot be imported
The document importer takes the export produced by the [Document The document importer takes the export produced by the [Document
exporter](#exporter) and imports it into paperless. exporter](#exporter) and imports it into paperless.
The importer works just like the exporter. You point it at a directory, The importer works just like the exporter. You point it at a directory or the generated .zip file,
and the script does the rest of the work: and the script does the rest of the work:
```shell ```shell
@@ -351,9 +351,6 @@ When you use the provided docker compose script, put the export inside
the `export` folder in your paperless source directory. Specify the `export` folder in your paperless source directory. Specify
`../export` as the `source`. `../export` as the `source`.
Note that .zip files (as can be generated from the exporter) are not supported. You must unzip them into
the target directory first.
!!! note !!! note
Importing from a previous version of Paperless may work, but for best Importing from a previous version of Paperless may work, but for best

View File

@@ -179,6 +179,7 @@ variables:
| ---------------------------- | ---------------------------------------------- | | ---------------------------- | ---------------------------------------------- |
| `DOCUMENT_ID` | Database primary key of the document | | `DOCUMENT_ID` | Database primary key of the document |
| `DOCUMENT_FILE_NAME` | Formatted filename, not including paths | | `DOCUMENT_FILE_NAME` | Formatted filename, not including paths |
| `DOCUMENT_TYPE` | The document type (if any) |
| `DOCUMENT_CREATED` | Date & time when document created | | `DOCUMENT_CREATED` | Date & time when document created |
| `DOCUMENT_MODIFIED` | Date & time when document was last modified | | `DOCUMENT_MODIFIED` | Date & time when document was last modified |
| `DOCUMENT_ADDED` | Date & time when document was added | | `DOCUMENT_ADDED` | Date & time when document was added |

View File

@@ -1,5 +1,145 @@
# Changelog # Changelog
## paperless-ngx 2.17.1
### Bug Fixes
- Fix: correct PAPERLESS_EMPTY_TRASH_DIR to Path [@shamoon](https://github.com/shamoon) ([#10227](https://github.com/paperless-ngx/paperless-ngx/pull/10227))
### All App Changes
- Fix: correct PAPERLESS_EMPTY_TRASH_DIR to Path [@shamoon](https://github.com/shamoon) ([#10227](https://github.com/paperless-ngx/paperless-ngx/pull/10227))
## paperless-ngx 2.17.0
### Breaking Changes
- Fix: restore expected pre-2.16 scheduled workflow offset behavior [@shamoon](https://github.com/shamoon) ([#10218](https://github.com/paperless-ngx/paperless-ngx/pull/10218))
### Features / Enhancements
- QoL: log version at startup, show backend vs frontend mismatch in system status [@shamoon](https://github.com/shamoon) ([#10214](https://github.com/paperless-ngx/paperless-ngx/pull/10214))
- Feature: add Persian translation [@shamoon](https://github.com/shamoon) ([#10183](https://github.com/paperless-ngx/paperless-ngx/pull/10183))
- Enhancement: support import of zipped export [@kaerbr](https://github.com/kaerbr) ([#10073](https://github.com/paperless-ngx/paperless-ngx/pull/10073))
### Bug Fixes
- Fix: more api fixes [@shamoon](https://github.com/shamoon) ([#10204](https://github.com/paperless-ngx/paperless-ngx/pull/10204))
- Fix: restore expected pre-2.16 scheduled workflow offset behavior [@shamoon](https://github.com/shamoon) ([#10218](https://github.com/paperless-ngx/paperless-ngx/pull/10218))
- Fix: fix some API crashes [@shamoon](https://github.com/shamoon) ([#10196](https://github.com/paperless-ngx/paperless-ngx/pull/10196))
- Fix: remove duplicate base path in websocket urls [@robertmx](https://github.com/robertmx) ([#10194](https://github.com/paperless-ngx/paperless-ngx/pull/10194))
- Fix: use hard delete for custom fields with workflow removal [@shamoon](https://github.com/shamoon) ([#10191](https://github.com/paperless-ngx/paperless-ngx/pull/10191))
- Fix: fix mail account test api schema [@shamoon](https://github.com/shamoon) ([#10164](https://github.com/paperless-ngx/paperless-ngx/pull/10164))
- Fix: correct api schema for mail_account process [@shamoon](https://github.com/shamoon) ([#10157](https://github.com/paperless-ngx/paperless-ngx/pull/10157))
- Fix: correct api schema for next_asn [@shamoon](https://github.com/shamoon) ([#10151](https://github.com/paperless-ngx/paperless-ngx/pull/10151))
- Fix: fix email and notes endpoints api spec [@shamoon](https://github.com/shamoon) ([#10148](https://github.com/paperless-ngx/paperless-ngx/pull/10148))
### Dependencies
- Chore: bump angular/common to 19.12.14 [@shamoon](https://github.com/shamoon) ([#10212](https://github.com/paperless-ngx/paperless-ngx/pull/10212))
### All App Changes
<details>
<summary>14 changes</summary>
- QoL: log version at startup, show backend vs frontend mismatch in system status [@shamoon](https://github.com/shamoon) ([#10214](https://github.com/paperless-ngx/paperless-ngx/pull/10214))
- Fix: more api fixes [@shamoon](https://github.com/shamoon) ([#10204](https://github.com/paperless-ngx/paperless-ngx/pull/10204))
- Fix: restore expected pre-2.16 scheduled workflow offset behavior [@shamoon](https://github.com/shamoon) ([#10218](https://github.com/paperless-ngx/paperless-ngx/pull/10218))
- Chore: switch from os.path to pathlib.Path [@gothicVI](https://github.com/gothicVI) ([#9933](https://github.com/paperless-ngx/paperless-ngx/pull/9933))
- Chore: bump angular/common to 19.12.14 [@shamoon](https://github.com/shamoon) ([#10212](https://github.com/paperless-ngx/paperless-ngx/pull/10212))
- Fix: fix some API crashes [@shamoon](https://github.com/shamoon) ([#10196](https://github.com/paperless-ngx/paperless-ngx/pull/10196))
- Fix: remove duplicate base path in websocket urls [@robertmx](https://github.com/robertmx) ([#10194](https://github.com/paperless-ngx/paperless-ngx/pull/10194))
- Fix: use hard delete for custom fields with workflow removal [@shamoon](https://github.com/shamoon) ([#10191](https://github.com/paperless-ngx/paperless-ngx/pull/10191))
- Feature: add Persian translation [@shamoon](https://github.com/shamoon) ([#10183](https://github.com/paperless-ngx/paperless-ngx/pull/10183))
- Enhancement: support import of zipped export [@kaerbr](https://github.com/kaerbr) ([#10073](https://github.com/paperless-ngx/paperless-ngx/pull/10073))
- Fix: fix mail account test api schema [@shamoon](https://github.com/shamoon) ([#10164](https://github.com/paperless-ngx/paperless-ngx/pull/10164))
- Fix: correct api schema for mail_account process [@shamoon](https://github.com/shamoon) ([#10157](https://github.com/paperless-ngx/paperless-ngx/pull/10157))
- Fix: correct api schema for next_asn [@shamoon](https://github.com/shamoon) ([#10151](https://github.com/paperless-ngx/paperless-ngx/pull/10151))
- Fix: fix email and notes endpoints api spec [@shamoon](https://github.com/shamoon) ([#10148](https://github.com/paperless-ngx/paperless-ngx/pull/10148))
</details>
## paperless-ngx 2.16.3
### Features / Enhancements
- Performance: pre-filter document list in scheduled workflow checks [@shamoon](https://github.com/shamoon) ([#10031](https://github.com/paperless-ngx/paperless-ngx/pull/10031))
- Chore: refactor consumer plugin checks to a pre-flight plugin [@shamoon](https://github.com/shamoon) ([#9994](https://github.com/paperless-ngx/paperless-ngx/pull/9994))
- Enhancement: include DOCUMENT_TYPE to post consume scripts [@matthesrieke](https://github.com/matthesrieke) ([#9977](https://github.com/paperless-ngx/paperless-ngx/pull/9977))
### Bug Fixes
- Fix: handle whoosh query correction errors [@shamoon](https://github.com/shamoon) ([#10121](https://github.com/paperless-ngx/paperless-ngx/pull/10121))
- Fix: handle favicon with non-default static dir [@shamoon](https://github.com/shamoon) ([#10107](https://github.com/paperless-ngx/paperless-ngx/pull/10107))
- Fixhancement: cleanup user or group references in settings upon deletion [@shamoon](https://github.com/shamoon) ([#10049](https://github.com/paperless-ngx/paperless-ngx/pull/10049))
- Fix: Add exception to in [@Freilichtbuehne](https://github.com/Freilichtbuehne) ([#10070](https://github.com/paperless-ngx/paperless-ngx/pull/10070))
- Fix: include base href when opening global search result in new window [@shamoon](https://github.com/shamoon) ([#10066](https://github.com/paperless-ngx/paperless-ngx/pull/10066))
### Dependencies
<details>
<summary>10 changes</summary>
- Chore(deps): Bump the small-changes group across 1 directory with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10085](https://github.com/paperless-ngx/paperless-ngx/pull/10085))
- Chore(deps): Update granian[uvloop] requirement from ~=2.2.0 to ~=2.3.2 @[dependabot[bot]](https://github.com/apps/dependabot) ([#10055](https://github.com/paperless-ngx/paperless-ngx/pull/10055))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 18 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10099](https://github.com/paperless-ngx/paperless-ngx/pull/10099))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10100](https://github.com/paperless-ngx/paperless-ngx/pull/10100))
- Chore(deps): Bump bootstrap from 5.3.3 to 5.3.6 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10091](https://github.com/paperless-ngx/paperless-ngx/pull/10091))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.9.0 to 1.9.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10090](https://github.com/paperless-ngx/paperless-ngx/pull/10090))
- Chore(deps-dev): Bump @types/node from 22.15.3 to 22.15.29 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10089](https://github.com/paperless-ngx/paperless-ngx/pull/10089))
- Chore(deps): Bump zone.js from 0.15.0 to 0.15.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10088](https://github.com/paperless-ngx/paperless-ngx/pull/10088))
- docker(deps): bump astral-sh/uv from 0.7.8-python3.12-bookworm-slim to 0.7.9-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#10084](https://github.com/paperless-ngx/paperless-ngx/pull/10084))
- docker(deps): Bump astral-sh/uv from 0.6.16-python3.12-bookworm-slim to 0.7.8-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#10056](https://github.com/paperless-ngx/paperless-ngx/pull/10056))
</details>
### All App Changes
<details>
<summary>16 changes</summary>
- Fix: handle whoosh query correction errors [@shamoon](https://github.com/shamoon) ([#10121](https://github.com/paperless-ngx/paperless-ngx/pull/10121))
- Performance: pre-filter document list in scheduled workflow checks [@shamoon](https://github.com/shamoon) ([#10031](https://github.com/paperless-ngx/paperless-ngx/pull/10031))
- Chore(deps): Bump the small-changes group across 1 directory with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10085](https://github.com/paperless-ngx/paperless-ngx/pull/10085))
- Chore: refactor consumer plugin checks to a pre-flight plugin [@shamoon](https://github.com/shamoon) ([#9994](https://github.com/paperless-ngx/paperless-ngx/pull/9994))
- Chore(deps): Update granian[uvloop] requirement from ~=2.2.0 to ~=2.3.2 @[dependabot[bot]](https://github.com/apps/dependabot) ([#10055](https://github.com/paperless-ngx/paperless-ngx/pull/10055))
- Fix: handle favicon with non-default static dir [@shamoon](https://github.com/shamoon) ([#10107](https://github.com/paperless-ngx/paperless-ngx/pull/10107))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 18 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10099](https://github.com/paperless-ngx/paperless-ngx/pull/10099))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#10100](https://github.com/paperless-ngx/paperless-ngx/pull/10100))
- Chore(deps): Bump bootstrap from 5.3.3 to 5.3.6 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10091](https://github.com/paperless-ngx/paperless-ngx/pull/10091))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.9.0 to 1.9.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10090](https://github.com/paperless-ngx/paperless-ngx/pull/10090))
- Chore(deps-dev): Bump @types/node from 22.15.3 to 22.15.29 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10089](https://github.com/paperless-ngx/paperless-ngx/pull/10089))
- Chore(deps): Bump zone.js from 0.15.0 to 0.15.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#10088](https://github.com/paperless-ngx/paperless-ngx/pull/10088))
- Fixhancement: cleanup user or group references in settings upon deletion [@shamoon](https://github.com/shamoon) ([#10049](https://github.com/paperless-ngx/paperless-ngx/pull/10049))
- Enhancement: include DOCUMENT_TYPE to post consume scripts [@matthesrieke](https://github.com/matthesrieke) ([#9977](https://github.com/paperless-ngx/paperless-ngx/pull/9977))
- Fix: Add exception to in [@Freilichtbuehne](https://github.com/Freilichtbuehne) ([#10070](https://github.com/paperless-ngx/paperless-ngx/pull/10070))
- Fix: include base href when opening global search result in new window [@shamoon](https://github.com/shamoon) ([#10066](https://github.com/paperless-ngx/paperless-ngx/pull/10066))
</details>
## paperless-ngx 2.16.2
### Bug Fixes
- Fix: accept datetime for created [@shamoon](https://github.com/shamoon) ([#10021](https://github.com/paperless-ngx/paperless-ngx/pull/10021))
- Fix: created date fixes in v2.16 [@shamoon](https://github.com/shamoon) ([#10026](https://github.com/paperless-ngx/paperless-ngx/pull/10026))
- Fix: mark fields for created objects as dirty [@shamoon](https://github.com/shamoon) ([#10022](https://github.com/paperless-ngx/paperless-ngx/pull/10022))
- Fix: add fallback to copyfile on PermissionError @samuel-kosmann ([#10023](https://github.com/paperless-ngx/paperless-ngx/pull/10023))
### Dependencies
- Chore: warn users about removal of postgres v13 support [@shamoon](https://github.com/shamoon) ([#9980](https://github.com/paperless-ngx/paperless-ngx/pull/9980))
### All App Changes
<details>
<summary>5 changes</summary>
- Fix: accept datetime for created [@shamoon](https://github.com/shamoon) ([#10021](https://github.com/paperless-ngx/paperless-ngx/pull/10021))
- Fix: add fallback to copyfile on PermissionError @samuel-kosmann ([#10023](https://github.com/paperless-ngx/paperless-ngx/pull/10023))
- Fix: created date fixes in v2.16 [@shamoon](https://github.com/shamoon) ([#10026](https://github.com/paperless-ngx/paperless-ngx/pull/10026))
- Fix: mark fields for created objects as dirty [@shamoon](https://github.com/shamoon) ([#10022](https://github.com/paperless-ngx/paperless-ngx/pull/10022))
- Chore: warn users about removal of postgres v13 support [@shamoon](https://github.com/shamoon) ([#9980](https://github.com/paperless-ngx/paperless-ngx/pull/9980))
</details>
## paperless-ngx 2.16.1 ## paperless-ngx 2.16.1
### Bug Fixes ### Bug Fixes
@@ -5867,7 +6007,6 @@ primarily.
a very good job at ocr'ing a document with the default a very good job at ocr'ing a document with the default
language. Certain language specifics such as umlauts may not get language. Certain language specifics such as umlauts may not get
picked up properly. picked up properly.
- `PAPERLESS_DEBUG` defaults to `false`.
- The presence of `PAPERLESS_DBHOST` now determines whether to use - The presence of `PAPERLESS_DBHOST` now determines whether to use
PostgreSQL or SQLite. PostgreSQL or SQLite.
- `PAPERLESS_OCR_THREADS` is gone and replaced with - `PAPERLESS_OCR_THREADS` is gone and replaced with

View File

@@ -50,47 +50,48 @@ matcher.
### Database ### Database
By default, Paperless uses **SQLite** with a database stored at `data/db.sqlite3`.
To switch to **PostgreSQL** or **MariaDB**, set [`PAPERLESS_DBHOST`](#PAPERLESS_DBHOST) and optionally configure other
database-related environment variables.
#### [`PAPERLESS_DBHOST=<hostname>`](#PAPERLESS_DBHOST) {#PAPERLESS_DBHOST}
: If unset, Paperless uses **SQLite** by default.
Set `PAPERLESS_DBHOST` to switch to PostgreSQL or MariaDB instead.
#### [`PAPERLESS_DBENGINE=<engine_name>`](#PAPERLESS_DBENGINE) {#PAPERLESS_DBENGINE} #### [`PAPERLESS_DBENGINE=<engine_name>`](#PAPERLESS_DBENGINE) {#PAPERLESS_DBENGINE}
: Optional, gives the ability to choose Postgres or MariaDB for : Optional. Specifies the database engine to use when connecting to a remote database.
database engine. Available options are `postgresql` and Available options are `postgresql` and `mariadb`.
`mariadb`.
Default is `postgresql`. Defaults to `postgresql` if `PAPERLESS_DBHOST` is set.
!!! warning !!! warning
Using MariaDB comes with some caveats. See [MySQL Caveats](advanced_usage.md#mysql-caveats). Using MariaDB comes with some caveats. See [MySQL Caveats](advanced_usage.md#mysql-caveats).
#### [`PAPERLESS_DBHOST=<hostname>`](#PAPERLESS_DBHOST) {#PAPERLESS_DBHOST}
: By default, sqlite is used as the database backend. This can be
changed here.
Set PAPERLESS_DBHOST and another database will be used instead of
sqlite.
#### [`PAPERLESS_DBPORT=<port>`](#PAPERLESS_DBPORT) {#PAPERLESS_DBPORT} #### [`PAPERLESS_DBPORT=<port>`](#PAPERLESS_DBPORT) {#PAPERLESS_DBPORT}
: Adjust port if necessary. : Port to use when connecting to PostgreSQL or MariaDB.
Default is 5432. Default is `5432` for PostgreSQL and `3306` for MariaDB.
#### [`PAPERLESS_DBNAME=<name>`](#PAPERLESS_DBNAME) {#PAPERLESS_DBNAME} #### [`PAPERLESS_DBNAME=<name>`](#PAPERLESS_DBNAME) {#PAPERLESS_DBNAME}
: Database name in PostgreSQL or MariaDB. : Name of the database to connect to when using PostgreSQL or MariaDB.
Defaults to "paperless". Defaults to "paperless".
#### [`PAPERLESS_DBUSER=<name>`](#PAPERLESS_DBUSER) {#PAPERLESS_DBUSER} #### [`PAPERLESS_DBUSER=<name>`](#PAPERLESS_DBUSER) {#PAPERLESS_DBUSER}
: Database user in PostgreSQL or MariaDB. : Username for authenticating with the PostgreSQL or MariaDB database.
Defaults to "paperless". Defaults to "paperless".
#### [`PAPERLESS_DBPASS=<password>`](#PAPERLESS_DBPASS) {#PAPERLESS_DBPASS} #### [`PAPERLESS_DBPASS=<password>`](#PAPERLESS_DBPASS) {#PAPERLESS_DBPASS}
: Database password for PostgreSQL or MariaDB. : Password for the PostgreSQL or MariaDB database user.
Defaults to "paperless". Defaults to "paperless".
@@ -110,20 +111,20 @@ changed here.
#### [`PAPERLESS_DBSSLROOTCERT=<ca-path>`](#PAPERLESS_DBSSLROOTCERT) {#PAPERLESS_DBSSLROOTCERT} #### [`PAPERLESS_DBSSLROOTCERT=<ca-path>`](#PAPERLESS_DBSSLROOTCERT) {#PAPERLESS_DBSSLROOTCERT}
: SSL root certificate path : Path to the SSL root certificate used to verify the database server.
See [the official documentation about See [the official documentation about
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html). sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
Changes path of `root.crt`. Changes the location of `root.crt`.
See [the official documentation about See [the official documentation about
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-ca). sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-ca).
Defaults to unset, using the documented path in the home directory. Defaults to unset, using the standard location in the home directory.
#### [`PAPERLESS_DBSSLCERT=<client-cert-path>`](#PAPERLESS_DBSSLCERT) {#PAPERLESS_DBSSLCERT} #### [`PAPERLESS_DBSSLCERT=<client-cert-path>`](#PAPERLESS_DBSSLCERT) {#PAPERLESS_DBSSLCERT}
: SSL client certificate path : Path to the client SSL certificate used when connecting securely.
See [the official documentation about See [the official documentation about
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html). sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
@@ -131,13 +132,13 @@ changed here.
See [the official documentation about See [the official documentation about
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-cert). sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-cert).
Changes path of `postgresql.crt`. Changes the location of `postgresql.crt`.
Defaults to unset, using the documented path in the home directory. Defaults to unset, using the standard location in the home directory.
#### [`PAPERLESS_DBSSLKEY=<client-cert-key>`](#PAPERLESS_DBSSLKEY) {#PAPERLESS_DBSSLKEY} #### [`PAPERLESS_DBSSLKEY=<client-cert-key>`](#PAPERLESS_DBSSLKEY) {#PAPERLESS_DBSSLKEY}
: SSL client key path : Path to the client SSL private key used when connecting securely.
See [the official documentation about See [the official documentation about
sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html). sslmode for PostgreSQL](https://www.postgresql.org/docs/current/libpq-ssl.html).
@@ -145,17 +146,18 @@ changed here.
See [the official documentation about See [the official documentation about
sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-key). sslmode for MySQL and MariaDB](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-key).
Changes path of `postgresql.key`. Changes the location of `postgresql.key`.
Defaults to unset, using the documented path in the home directory. Defaults to unset, using the standard location in the home directory.
#### [`PAPERLESS_DB_TIMEOUT=<int>`](#PAPERLESS_DB_TIMEOUT) {#PAPERLESS_DB_TIMEOUT} #### [`PAPERLESS_DB_TIMEOUT=<int>`](#PAPERLESS_DB_TIMEOUT) {#PAPERLESS_DB_TIMEOUT}
: Amount of time for a database connection to wait for the database to : Sets how long a database connection should wait before timing out.
unlock. Mostly applicable for sqlite based installation. Consider changing
to postgresql if you are having concurrency problems with sqlite.
Defaults to unset, keeping the Django defaults. For SQLite, this sets how long to wait if the database is locked.
For PostgreSQL or MariaDB, this sets the connection timeout.
Defaults to unset, which uses Djangos built-in defaults.
## Optional Services ## Optional Services
@@ -200,7 +202,7 @@ and watch out for indentation if editing the YAML file.
### Email Parsing ### Email Parsing
#### [`PAPERLESS_EMAIL_PARSE_DEFAULT_LAYOUT=<int>`(#PAPERLESS_EMAIL_PARSE_DEFAULT_LAYOUT) {#PAPERLESS_EMAIL_PARSE_DEFAULT_LAYOUT} #### [`PAPERLESS_EMAIL_PARSE_DEFAULT_LAYOUT=<int>`](#PAPERLESS_EMAIL_PARSE_DEFAULT_LAYOUT) {#PAPERLESS_EMAIL_PARSE_DEFAULT_LAYOUT}
: The default layout to use for emails that are consumed as documents. Must be one of the integer choices below. Note that mail : The default layout to use for emails that are consumed as documents. Must be one of the integer choices below. Note that mail
rules can specify this setting, thus this fallback is used for the default selection and for .eml files consumed by other means. rules can specify this setting, thus this fallback is used for the default selection and for .eml files consumed by other means.

View File

@@ -130,7 +130,7 @@ command:
- 'gotenberg' - 'gotenberg'
- '--chromium-disable-javascript=true' - '--chromium-disable-javascript=true'
- '--chromium-allow-list=file:///tmp/.*' - '--chromium-allow-list=file:///tmp/.*'
- '--api-timeout=60' - '--api-timeout=60s'
``` ```
## Permission denied errors in the consumption directory ## Permission denied errors in the consumption directory

View File

@@ -338,25 +338,25 @@ Global permissions define what areas of the app and API endpoints users can acce
determine if a user can create, edit, delete or view _any_ documents, but individual documents themselves determine if a user can create, edit, delete or view _any_ documents, but individual documents themselves
still have "object-level" permissions. still have "object-level" permissions.
| Type | Details | | Type | Details |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AppConfig | _Change_ or higher permissions grants access to the "Application Configuration" area. | | AppConfig | _Change_ or higher permissions grants access to the "Application Configuration" area. |
| Correspondent | Add, edit, delete or view Correspondents. | | Correspondent | Add, edit, delete or view Correspondents. |
| CustomField | Add, edit, delete or view Custom Fields. | | CustomField | Add, edit, delete or view Custom Fields. |
| Document | Add, edit, delete or view Documents. | | Document | Add, edit, delete or view Documents. |
| DocumentType | Add, edit, delete or view Document Types. | | DocumentType | Add, edit, delete or view Document Types. |
| Group | Add, edit, delete or view Groups. | | Group | Add, edit, delete or view Groups. |
| MailAccount | Add, edit, delete or view Mail Accounts. | | MailAccount | Add, edit, delete or view Mail Accounts. |
| MailRule | Add, edit, delete or view Mail Rules. | | MailRule | Add, edit, delete or view Mail Rules. |
| Note | Add, edit, delete or view Notes. | | Note | Add, edit, delete or view Notes. |
| PaperlessTask | View or dismiss (_Change_) File Tasks. | | PaperlessTask | View or dismiss (_Change_) File Tasks. |
| SavedView | Add, edit, delete or view Saved Views. | | SavedView | Add, edit, delete or view Saved Views. |
| ShareLink | Add, delete or view Share Links. | | ShareLink | Add, delete or view Share Links. |
| StoragePath | Add, edit, delete or view Storage Paths. | | StoragePath | Add, edit, delete or view Storage Paths. |
| Tag | Add, edit, delete or view Tags. | | Tag | Add, edit, delete or view Tags. |
| UISettings | Add, edit, delete or view the UI settings that are used by the web app.<br/>:warning: **Users that will access the web UI must be granted at least _View_ permissions.** | | UISettings | Add, edit, delete or view the UI settings that are used by the web app.<br/>:warning: **Users that will access the web UI must be granted at least _View_ permissions.** |
| User | Add, edit, delete or view Users. | | User | Add, edit, delete or view Users. |
| Workflow | Add, edit, delete or view Workflows.<br/>Note that Workflows are global, in other words all users who can access workflows have access to the same set of them. | | Workflow | Add, edit, delete or view Workflows.<br/>Note that Workflows are global; all users who can access workflows see the same set. Workflows have other permission implications — see [Workflow permissions](#workflow-permissions). |
#### Detailed Explanation of Object Permissions {#object-permissions} #### Detailed Explanation of Object Permissions {#object-permissions}
@@ -408,7 +408,7 @@ Currently, there are three events that correspond to workflow trigger 'types':
tags, doc type, or correspondent. tags, doc type, or correspondent.
4. **Scheduled**: a scheduled trigger that can be used to run workflows at a specific time. The date used can be either the document 4. **Scheduled**: a scheduled trigger that can be used to run workflows at a specific time. The date used can be either the document
added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date (positive added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date (positive
offsets will trigger before the date, negative offsets will trigger after). offsets will trigger after the date, negative offsets will trigger before).
The following flow diagram illustrates the three document trigger types: The following flow diagram illustrates the three document trigger types:
@@ -533,7 +533,7 @@ The following placeholders are only available for "added" or "updated" triggers
All users who have application permissions for editing workflows can see the same set All users who have application permissions for editing workflows can see the same set
of workflows. In other words, workflows themselves intentionally do not have an owner or permissions. of workflows. In other words, workflows themselves intentionally do not have an owner or permissions.
Given their potentially far-reaching capabilities, you may want to restrict access to workflows. Given their potentially far-reaching capabilities, including changing the permissions of existing documents, you may want to restrict access to workflows.
Upon migration, existing installs will grant access to workflows to users who can add Upon migration, existing installs will grant access to workflows to users who can add
documents (and superusers who can always access all parts of the app). documents (and superusers who can always access all parts of the app).

View File

@@ -1,10 +1,6 @@
# Have a look at the docs for documentation. # Have a look at the docs for documentation.
# https://docs.paperless-ngx.com/configuration/ # https://docs.paperless-ngx.com/configuration/
# Debug. Only enable this for development.
#PAPERLESS_DEBUG=false
# Required services # Required services
#PAPERLESS_REDIS=redis://localhost:6379 #PAPERLESS_REDIS=redis://localhost:6379

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "paperless-ngx" name = "paperless-ngx"
version = "2.16.2" version = "2.17.1"
description = "A community-supported supercharged version of paperless: scan, index and archive all your physical documents" description = "A community-supported supercharged version of paperless: scan, index and archive all your physical documents"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
@@ -78,7 +78,7 @@ optional-dependencies.postgres = [
"psycopg-c==3.2.5", "psycopg-c==3.2.5",
] ]
optional-dependencies.webserver = [ optional-dependencies.webserver = [
"granian[uvloop]~=2.2.0", "granian[uvloop]~=2.3.2",
] ]
[dependency-groups] [dependency-groups]
@@ -221,34 +221,9 @@ lint.per-file-ignores."src/documents/parsers.py" = [
lint.per-file-ignores."src/documents/signals/handlers.py" = [ lint.per-file-ignores."src/documents/signals/handlers.py" = [
"PTH", "PTH",
] # TODO Enable & remove ] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_consumer.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_file_handling.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_migration_archive_files.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/views.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/checks.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/settings.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless/views.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_mail/mail.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [ lint.per-file-ignores."src/paperless_tesseract/tests/test_parser.py" = [
"PTH",
"RUF001", "RUF001",
] # TODO PTH Enable & remove ]
lint.isort.force-single-line = true lint.isort.force-single-line = true
[tool.pytest.ini_options] [tool.pytest.ini_options]

View File

@@ -6,6 +6,7 @@ A document with an id of ${DOCUMENT_ID} was just consumed. I know the
following additional information about it: following additional information about it:
* Generated File Name: ${DOCUMENT_FILE_NAME} * Generated File Name: ${DOCUMENT_FILE_NAME}
* Document type: ${DOCUMENT_TYPE}
* Archive Path: ${DOCUMENT_ARCHIVE_PATH} * Archive Path: ${DOCUMENT_ARCHIVE_PATH}
* Source Path: ${DOCUMENT_SOURCE_PATH} * Source Path: ${DOCUMENT_SOURCE_PATH}
* Created: ${DOCUMENT_CREATED} * Created: ${DOCUMENT_CREATED}

View File

@@ -27,6 +27,7 @@
"el-GR": "src/locale/messages.el_GR.xlf", "el-GR": "src/locale/messages.el_GR.xlf",
"en-GB": "src/locale/messages.en_GB.xlf", "en-GB": "src/locale/messages.en_GB.xlf",
"es-ES": "src/locale/messages.es_ES.xlf", "es-ES": "src/locale/messages.es_ES.xlf",
"fa-IR": "src/locale/messages.fa_IR.xlf",
"fi-FI": "src/locale/messages.fi_FI.xlf", "fi-FI": "src/locale/messages.fi_FI.xlf",
"fr-FR": "src/locale/messages.fr_FR.xlf", "fr-FR": "src/locale/messages.fr_FR.xlf",
"hu-HU": "src/locale/messages.hu_HU.xlf", "hu-HU": "src/locale/messages.hu_HU.xlf",
@@ -59,10 +60,12 @@
"path": "./extra-webpack.config.ts" "path": "./extra-webpack.config.ts"
}, },
"outputPath": "dist/paperless-ui", "outputPath": "dist/paperless-ui",
"main": "src/main.ts",
"outputHashing": "none", "outputHashing": "none",
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "polyfills": [
"polyfills": "src/polyfills.ts", "src/polyfills.ts"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"localize": true, "localize": true,
"assets": [ "assets": [
@@ -85,12 +88,15 @@
"file-saver", "file-saver",
"utif" "utif"
], ],
"vendorChunk": true,
"extractLicenses": false, "extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true, "sourceMap": true,
"optimization": false, "optimization": false,
"namedChunks": true "namedChunks": true,
"stylePreprocessorOptions": {
"includePaths": [
"."
]
}
}, },
"configurations": { "configurations": {
"production": { "production": {
@@ -106,8 +112,6 @@
"sourceMap": false, "sourceMap": false,
"namedChunks": false, "namedChunks": false,
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
@@ -187,6 +191,30 @@
}, },
"@angular-eslint/schematics:library": { "@angular-eslint/schematics:library": {
"setParserOptionsProject": true "setParserOptionsProject": true
},
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
} }
} }
} }

View File

@@ -8,7 +8,7 @@ module.exports = {
'abstract-paperless-service', 'abstract-paperless-service',
], ],
transformIgnorePatterns: [ transformIgnorePatterns: [
`<rootDir>/node_modules/.pnpm/(?!.*\\.mjs$|lodash-es)`, `<rootDir>/node_modules/.pnpm/(?!.*\\.mjs$|lodash-es|@angular\\+common.*locales)`,
], ],
moduleNameMapper: { moduleNameMapper: {
'^src/(.*)': '<rootDir>/src/$1', '^src/(.*)': '<rootDir>/src/$1',

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "paperless-ngx-ui", "name": "paperless-ngx-ui",
"version": "2.16.2", "version": "2.17.1",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"ng": "ng", "ng": "ng",
@@ -11,20 +11,20 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/cdk": "^19.2.14", "@angular/cdk": "^20.0.4",
"@angular/common": "~19.2.9", "@angular/common": "~20.0.5",
"@angular/compiler": "~19.2.9", "@angular/compiler": "~20.0.5",
"@angular/core": "~19.2.9", "@angular/core": "~20.0.5",
"@angular/forms": "~19.2.9", "@angular/forms": "~20.0.5",
"@angular/localize": "~19.2.9", "@angular/localize": "~20.0.5",
"@angular/platform-browser": "~19.2.9", "@angular/platform-browser": "~20.0.5",
"@angular/platform-browser-dynamic": "~19.2.9", "@angular/platform-browser-dynamic": "~20.0.5",
"@angular/router": "~19.2.9", "@angular/router": "~20.0.5",
"@ng-bootstrap/ng-bootstrap": "^18.0.0", "@ng-bootstrap/ng-bootstrap": "^19.0.0",
"@ng-select/ng-select": "^14.7.0", "@ng-select/ng-select": "^15.1.2",
"@ngneat/dirty-check-forms": "^3.0.3", "@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.6",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"mime-names": "^1.0.0", "mime-names": "^1.0.0",
"ng2-pdf-viewer": "^10.4.0", "ng2-pdf-viewer": "^10.4.0",
@@ -32,34 +32,34 @@
"ngx-color": "^10.0.0", "ngx-color": "^10.0.0",
"ngx-cookie-service": "^19.1.2", "ngx-cookie-service": "^19.1.2",
"ngx-device-detector": "^9.0.0", "ngx-device-detector": "^9.0.0",
"ngx-ui-tour-ng-bootstrap": "^16.0.0", "ngx-ui-tour-ng-bootstrap": "^17.0.0",
"rxjs": "^7.8.2", "rxjs": "^7.8.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"utif": "^3.1.0", "utif": "^3.1.0",
"uuid": "^11.1.0", "uuid": "^11.1.0",
"zone.js": "^0.15.0" "zone.js": "^0.15.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "^19.0.1", "@angular-builders/custom-webpack": "^20.0.0",
"@angular-builders/jest": "^19.0.1", "@angular-builders/jest": "^20.0.0",
"@angular-devkit/build-angular": "^19.2.10", "@angular-devkit/core": "^20.0.4",
"@angular-devkit/core": "^19.2.10", "@angular-devkit/schematics": "^20.0.4",
"@angular-devkit/schematics": "^19.2.10", "@angular-eslint/builder": "20.1.1",
"@angular-eslint/builder": "19.3.0", "@angular-eslint/eslint-plugin": "20.1.1",
"@angular-eslint/eslint-plugin": "19.3.0", "@angular-eslint/eslint-plugin-template": "20.1.1",
"@angular-eslint/eslint-plugin-template": "19.3.0", "@angular-eslint/schematics": "20.1.1",
"@angular-eslint/schematics": "19.3.0", "@angular-eslint/template-parser": "20.1.1",
"@angular-eslint/template-parser": "19.3.0", "@angular/build": "^20.0.4",
"@angular/cli": "~19.2.10", "@angular/cli": "~20.0.4",
"@angular/compiler-cli": "~19.2.9", "@angular/compiler-cli": "~20.0.5",
"@codecov/webpack-plugin": "^1.9.0", "@codecov/webpack-plugin": "^1.9.1",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.15.3", "@types/node": "^22.15.29",
"@typescript-eslint/eslint-plugin": "^8.31.1", "@typescript-eslint/eslint-plugin": "^8.33.1",
"@typescript-eslint/parser": "^8.31.1", "@typescript-eslint/parser": "^8.33.1",
"@typescript-eslint/utils": "^8.31.1", "@typescript-eslint/utils": "^8.33.1",
"eslint": "^9.25.1", "eslint": "^9.28.0",
"jest": "29.7.0", "jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
"jest-junit": "^16.0.0", "jest-junit": "^16.0.0",
@@ -67,7 +67,8 @@
"jest-websocket-mock": "^2.5.0", "jest-websocket-mock": "^2.5.0",
"prettier-plugin-organize-imports": "^4.1.0", "prettier-plugin-organize-imports": "^4.1.0",
"ts-node": "~10.9.1", "ts-node": "~10.9.1",
"typescript": "^5.5.4" "typescript": "^5.8.3",
"webpack": "^5.98.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
@@ -77,6 +78,5 @@
"lmdb", "lmdb",
"msgpackr-extract" "msgpackr-extract"
] ]
}, }
"typings": "./src/typings.d.ts"
} }

4650
src-ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@ import localeDe from '@angular/common/locales/de'
import localeEl from '@angular/common/locales/el' import localeEl from '@angular/common/locales/el'
import localeEnGb from '@angular/common/locales/en-GB' import localeEnGb from '@angular/common/locales/en-GB'
import localeEs from '@angular/common/locales/es' import localeEs from '@angular/common/locales/es'
import localeFa from '@angular/common/locales/fa'
import localeFi from '@angular/common/locales/fi' import localeFi from '@angular/common/locales/fi'
import localeFr from '@angular/common/locales/fr' import localeFr from '@angular/common/locales/fr'
import localeHu from '@angular/common/locales/hu' import localeHu from '@angular/common/locales/hu'
@@ -53,6 +54,7 @@ registerLocaleData(localeDe)
registerLocaleData(localeEl) registerLocaleData(localeEl)
registerLocaleData(localeEnGb) registerLocaleData(localeEnGb)
registerLocaleData(localeEs) registerLocaleData(localeEs)
registerLocaleData(localeFa)
registerLocaleData(localeFi) registerLocaleData(localeFi)
registerLocaleData(localeFr) registerLocaleData(localeFr)
registerLocaleData(localeHu) registerLocaleData(localeHu)

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core' import { Component, inject, OnDestroy, OnInit, Renderer2 } from '@angular/core'
import { Router, RouterOutlet } from '@angular/router' import { Router, RouterOutlet } from '@angular/router'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { first, Subscription } from 'rxjs' import { first, Subscription } from 'rxjs'
@@ -29,22 +29,22 @@ import { WebsocketStatusService } from './services/websocket-status.service'
], ],
}) })
export class AppComponent implements OnInit, OnDestroy { export class AppComponent implements OnInit, OnDestroy {
private settings = inject(SettingsService)
private websocketStatusService = inject(WebsocketStatusService)
private toastService = inject(ToastService)
private router = inject(Router)
private tasksService = inject(TasksService)
tourService = inject(TourService)
private renderer = inject(Renderer2)
private permissionsService = inject(PermissionsService)
private hotKeyService = inject(HotKeyService)
private componentRouterService = inject(ComponentRouterService)
newDocumentSubscription: Subscription newDocumentSubscription: Subscription
successSubscription: Subscription successSubscription: Subscription
failedSubscription: Subscription failedSubscription: Subscription
constructor( constructor() {
private settings: SettingsService,
private websocketStatusService: WebsocketStatusService,
private toastService: ToastService,
private router: Router,
private tasksService: TasksService,
public tourService: TourService,
private renderer: Renderer2,
private permissionsService: PermissionsService,
private hotKeyService: HotKeyService,
private componentRouterService: ComponentRouterService
) {
let anyWindow = window as any let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.mjs' anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.mjs'
this.settings.updateAppearanceSettings() this.settings.updateAppearanceSettings()

View File

@@ -1,5 +1,5 @@
import { AsyncPipe } from '@angular/common' import { AsyncPipe } from '@angular/common'
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import { import {
AbstractControl, AbstractControl,
FormControl, FormControl,
@@ -57,6 +57,10 @@ export class ConfigComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit, OnDestroy, DirtyComponent implements OnInit, OnDestroy, DirtyComponent
{ {
private configService = inject(ConfigService)
private toastService = inject(ToastService)
private settingsService = inject(SettingsService)
public readonly ConfigOptionType = ConfigOptionType public readonly ConfigOptionType = ConfigOptionType
// generated dynamically // generated dynamically
@@ -77,11 +81,7 @@ export class ConfigComponent
storeSub: Subscription storeSub: Subscription
isDirty$: Observable<boolean> isDirty$: Observable<boolean>
constructor( constructor() {
private configService: ConfigService,
private toastService: ToastService,
private settingsService: SettingsService
) {
super() super()
this.configForm.addControl('id', new FormControl()) this.configForm.addControl('id', new FormControl())
PaperlessConfigOptions.forEach((option) => { PaperlessConfigOptions.forEach((option) => {

View File

@@ -5,6 +5,7 @@ import {
OnDestroy, OnDestroy,
OnInit, OnInit,
ViewChild, ViewChild,
inject,
} from '@angular/core' } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap' import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'
@@ -28,12 +29,8 @@ export class LogsComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
constructor( private logService = inject(LogService)
private logService: LogService, private changedetectorRef = inject(ChangeDetectorRef)
private changedetectorRef: ChangeDetectorRef
) {
super()
}
public logs: string[] = [] public logs: string[] = []

View File

@@ -176,6 +176,7 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check> <pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check>
<pngx-input-check i18n-title title="Show document counts in sidebar saved views" formControlName="sidebarViewsShowCount"></pngx-input-check>
</div> </div>
</div> </div>

View File

@@ -31,6 +31,7 @@ import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { PermissionsService } from 'src/app/services/permissions.service' import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { SystemStatusService } from 'src/app/services/system-status.service' import { SystemStatusService } from 'src/app/services/system-status.service'
@@ -72,6 +73,7 @@ describe('SettingsComponent', () => {
let groupService: GroupService let groupService: GroupService
let modalService: NgbModal let modalService: NgbModal
let systemStatusService: SystemStatusService let systemStatusService: SystemStatusService
let savedViewsService: SavedViewService
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -122,6 +124,7 @@ describe('SettingsComponent', () => {
permissionsService = TestBed.inject(PermissionsService) permissionsService = TestBed.inject(PermissionsService)
modalService = TestBed.inject(NgbModal) modalService = TestBed.inject(NgbModal)
systemStatusService = TestBed.inject(SystemStatusService) systemStatusService = TestBed.inject(SystemStatusService)
savedViewsService = TestBed.inject(SavedViewService)
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions') .spyOn(permissionsService, 'currentUserHasObjectPermissions')
@@ -212,7 +215,7 @@ describe('SettingsComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled() expect(toastErrorSpy).toHaveBeenCalled()
expect(storeSpy).toHaveBeenCalled() expect(storeSpy).toHaveBeenCalled()
expect(appearanceSettingsSpy).not.toHaveBeenCalled() expect(appearanceSettingsSpy).not.toHaveBeenCalled()
expect(setSpy).toHaveBeenCalledTimes(29) expect(setSpy).toHaveBeenCalledTimes(30)
// succeed // succeed
storeSpy.mockReturnValueOnce(of(true)) storeSpy.mockReturnValueOnce(of(true))
@@ -345,4 +348,14 @@ describe('SettingsComponent', () => {
component.reset() component.reset()
expect(component.settingsForm.get('themeColor').value).toEqual('') expect(component.settingsForm.get('themeColor').value).toEqual('')
}) })
it('should trigger maybeRefreshDocumentCounts on settings save', () => {
completeSetup()
const maybeRefreshSpy = jest.spyOn(
savedViewsService,
'maybeRefreshDocumentCounts'
)
settingsService.settingsSaved.emit(true)
expect(maybeRefreshSpy).toHaveBeenCalled()
})
}) })

View File

@@ -2,10 +2,10 @@ import { AsyncPipe, ViewportScroller } from '@angular/common'
import { import {
AfterViewInit, AfterViewInit,
Component, Component,
Inject,
LOCALE_ID, LOCALE_ID,
OnDestroy, OnDestroy,
OnInit, OnInit,
inject,
} from '@angular/core' } from '@angular/core'
import { import {
FormControl, FormControl,
@@ -49,6 +49,7 @@ import {
PermissionsService, PermissionsService,
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { import {
LanguageOption, LanguageOption,
@@ -104,6 +105,21 @@ export class SettingsComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
{ {
private documentListViewService = inject(DocumentListViewService)
private toastService = inject(ToastService)
private settings = inject(SettingsService)
currentLocale = inject(LOCALE_ID)
private viewportScroller = inject(ViewportScroller)
private activatedRoute = inject(ActivatedRoute)
readonly tourService = inject(TourService)
private usersService = inject(UserService)
private groupsService = inject(GroupService)
private router = inject(Router)
permissionsService = inject(PermissionsService)
private modalService = inject(NgbModal)
private systemStatusService = inject(SystemStatusService)
private savedViewsService = inject(SavedViewService)
activeNavID: number activeNavID: number
settingsForm = new FormGroup({ settingsForm = new FormGroup({
@@ -138,6 +154,7 @@ export class SettingsComponent
notificationsConsumerSuppressOnDashboard: new FormControl(null), notificationsConsumerSuppressOnDashboard: new FormControl(null),
savedViewsWarnOnUnsavedChange: new FormControl(null), savedViewsWarnOnUnsavedChange: new FormControl(null),
sidebarViewsShowCount: new FormControl(null),
}) })
SettingsNavIDs = SettingsNavIDs SettingsNavIDs = SettingsNavIDs
@@ -179,24 +196,11 @@ export class SettingsComponent
) )
} }
constructor( constructor() {
private documentListViewService: DocumentListViewService,
private toastService: ToastService,
private settings: SettingsService,
@Inject(LOCALE_ID) public currentLocale: string,
private viewportScroller: ViewportScroller,
private activatedRoute: ActivatedRoute,
public readonly tourService: TourService,
private usersService: UserService,
private groupsService: GroupService,
private router: Router,
public permissionsService: PermissionsService,
private modalService: NgbModal,
private systemStatusService: SystemStatusService
) {
super() super()
this.settings.settingsSaved.subscribe(() => { this.settings.settingsSaved.subscribe(() => {
if (!this.savePending) this.initialize() if (!this.savePending) this.initialize()
this.savedViewsService.maybeRefreshDocumentCounts()
}) })
} }
@@ -308,6 +312,9 @@ export class SettingsComponent
savedViewsWarnOnUnsavedChange: this.settings.get( savedViewsWarnOnUnsavedChange: this.settings.get(
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE
), ),
sidebarViewsShowCount: this.settings.get(
SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT
),
defaultPermsOwner: this.settings.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER), defaultPermsOwner: this.settings.get(SETTINGS_KEYS.DEFAULT_PERMS_OWNER),
defaultPermsViewUsers: this.settings.get( defaultPermsViewUsers: this.settings.get(
SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS SETTINGS_KEYS.DEFAULT_PERMS_VIEW_USERS
@@ -485,6 +492,10 @@ export class SettingsComponent
SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE, SETTINGS_KEYS.SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE,
this.settingsForm.value.savedViewsWarnOnUnsavedChange this.settingsForm.value.savedViewsWarnOnUnsavedChange
) )
this.settings.set(
SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT,
this.settingsForm.value.sidebarViewsShowCount
)
this.settings.set( this.settings.set(
SETTINGS_KEYS.DEFAULT_PERMS_OWNER, SETTINGS_KEYS.DEFAULT_PERMS_OWNER,
this.settingsForm.value.defaultPermsOwner this.settingsForm.value.defaultPermsOwner

View File

@@ -1,5 +1,5 @@
import { NgTemplateOutlet, SlicePipe } from '@angular/common' import { NgTemplateOutlet, SlicePipe } from '@angular/common'
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, inject, OnDestroy, OnInit } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { import {
@@ -69,6 +69,10 @@ export class TasksComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
tasksService = inject(TasksService)
private modalService = inject(NgbModal)
private readonly router = inject(Router)
public activeTab: TaskTab public activeTab: TaskTab
public selectedTasks: Set<number> = new Set() public selectedTasks: Set<number> = new Set()
public togggleAll: boolean = false public togggleAll: boolean = false
@@ -105,14 +109,6 @@ export class TasksComponent
: $localize`Dismiss all` : $localize`Dismiss all`
} }
constructor(
public tasksService: TasksService,
private modalService: NgbModal,
private readonly router: Router
) {
super()
}
ngOnInit() { ngOnInit() {
this.tasksService.reload() this.tasksService.reload()
timer(5000, 5000) timer(5000, 5000)

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy } from '@angular/core' import { Component, OnDestroy, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router' import { Router } from '@angular/router'
import { import {
@@ -36,19 +36,19 @@ export class TrashComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnDestroy implements OnDestroy
{ {
private trashService = inject(TrashService)
private toastService = inject(ToastService)
private modalService = inject(NgbModal)
private settingsService = inject(SettingsService)
private router = inject(Router)
public documentsInTrash: Document[] = [] public documentsInTrash: Document[] = []
public selectedDocuments: Set<number> = new Set() public selectedDocuments: Set<number> = new Set()
public allToggled: boolean = false public allToggled: boolean = false
public page: number = 1 public page: number = 1
public totalDocuments: number public totalDocuments: number
constructor( constructor() {
private trashService: TrashService,
private toastService: ToastService,
private modalService: NgbModal,
private settingsService: SettingsService,
private router: Router
) {
super() super()
this.reload() this.reload()
} }

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, first, takeUntil } from 'rxjs' import { Subject, first, takeUntil } from 'rxjs'
@@ -31,22 +31,18 @@ export class UsersAndGroupsComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
private usersService = inject(UserService)
private groupsService = inject(GroupService)
private toastService = inject(ToastService)
private modalService = inject(NgbModal)
permissionsService = inject(PermissionsService)
private settings = inject(SettingsService)
users: User[] users: User[]
groups: Group[] groups: Group[]
unsubscribeNotifier: Subject<any> = new Subject() unsubscribeNotifier: Subject<any> = new Subject()
constructor(
private usersService: UserService,
private groupsService: GroupService,
private toastService: ToastService,
private modalService: NgbModal,
public permissionsService: PermissionsService,
private settings: SettingsService
) {
super()
}
ngOnInit(): void { ngOnInit(): void {
this.usersService this.usersService
.listAll(null, null, { full_perms: true }) .listAll(null, null, { full_perms: true })

View File

@@ -112,7 +112,14 @@
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
popoverClass="popover-slim"> popoverClass="popover-slim">
<i-bs class="me-1" name="funnel"></i-bs><span>&nbsp;{{view.name}}</span> <i-bs class="me-1" name="funnel"></i-bs><span>&nbsp;{{view.name}}
@if (showSidebarCounts && !slimSidebarEnabled) {
<span><span class="badge bg-info text-dark ms-2 d-inline">{{ savedViewService.getDocumentCount(view) }}</span></span>
}
</span>
@if (showSidebarCounts && slimSidebarEnabled) {
<span class="badge bg-info text-dark position-absolute top-0 end-0 d-none d-md-block">{{ savedViewService.getDocumentCount(view) }}</span>
}
</a> </a>
@if (settingsService.organizingSidebarSavedViews) { @if (settingsService.organizingSidebarSavedViews) {
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle> <div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>

View File

@@ -92,6 +92,7 @@ describe('AppFrameComponent', () => {
let router: Router let router: Router
let savedViewSpy let savedViewSpy
let modalService: NgbModal let modalService: NgbModal
let maybeRefreshSpy
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -113,7 +114,11 @@ describe('AppFrameComponent', () => {
{ {
provide: SavedViewService, provide: SavedViewService,
useValue: { useValue: {
reload: () => {}, reload: (fn: any) => {
if (fn) {
fn()
}
},
listAll: () => listAll: () =>
of({ of({
all: [saved_views.map((v) => v.id)], all: [saved_views.map((v) => v.id)],
@@ -121,6 +126,8 @@ describe('AppFrameComponent', () => {
results: saved_views, results: saved_views,
}), }),
sidebarViews: saved_views.filter((v) => v.show_in_sidebar), sidebarViews: saved_views.filter((v) => v.show_in_sidebar),
getDocumentCount: (view: SavedView) => 5,
maybeRefreshDocumentCounts: () => {},
}, },
}, },
PermissionsService, PermissionsService,
@@ -169,6 +176,7 @@ describe('AppFrameComponent', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
savedViewSpy = jest.spyOn(savedViewService, 'reload') savedViewSpy = jest.spyOn(savedViewService, 'reload')
maybeRefreshSpy = jest.spyOn(savedViewService, 'maybeRefreshDocumentCounts')
fixture = TestBed.createComponent(AppFrameComponent) fixture = TestBed.createComponent(AppFrameComponent)
component = fixture.componentInstance component = fixture.componentInstance
@@ -359,4 +367,8 @@ describe('AppFrameComponent', () => {
expect(toastErrorSpy).toHaveBeenCalledTimes(2) expect(toastErrorSpy).toHaveBeenCalledTimes(2)
expect(toastInfoSpy).toHaveBeenCalledTimes(3) expect(toastInfoSpy).toHaveBeenCalledTimes(3)
}) })
it('should call maybeRefreshDocumentCounts after saved views reload', () => {
expect(maybeRefreshSpy).toHaveBeenCalled()
})
}) })

View File

@@ -6,7 +6,7 @@ import {
moveItemInArray, moveItemInArray,
} from '@angular/cdk/drag-drop' } from '@angular/cdk/drag-drop'
import { NgClass } from '@angular/common' import { NgClass } from '@angular/common'
import { Component, HostListener, OnInit } from '@angular/core' import { Component, HostListener, inject, OnInit } from '@angular/core'
import { ActivatedRoute, Router, RouterModule } from '@angular/router' import { ActivatedRoute, Router, RouterModule } from '@angular/router'
import { import {
NgbCollapseModule, NgbCollapseModule,
@@ -74,27 +74,27 @@ export class AppFrameComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, ComponentCanDeactivate implements OnInit, ComponentCanDeactivate
{ {
versionString = `${environment.appTitle} ${environment.version}` router = inject(Router)
private activatedRoute = inject(ActivatedRoute)
private openDocumentsService = inject(OpenDocumentsService)
savedViewService = inject(SavedViewService)
private remoteVersionService = inject(RemoteVersionService)
settingsService = inject(SettingsService)
tasksService = inject(TasksService)
private readonly toastService = inject(ToastService)
private modalService = inject(NgbModal)
permissionsService = inject(PermissionsService)
private djangoMessagesService = inject(DjangoMessagesService)
appRemoteVersion: AppRemoteVersion appRemoteVersion: AppRemoteVersion
isMenuCollapsed: boolean = true isMenuCollapsed: boolean = true
slimSidebarAnimating: boolean = false slimSidebarAnimating: boolean = false
constructor( constructor() {
public router: Router,
private activatedRoute: ActivatedRoute,
private openDocumentsService: OpenDocumentsService,
public savedViewService: SavedViewService,
private remoteVersionService: RemoteVersionService,
public settingsService: SettingsService,
public tasksService: TasksService,
private readonly toastService: ToastService,
private modalService: NgbModal,
public permissionsService: PermissionsService,
private djangoMessagesService: DjangoMessagesService
) {
super() super()
const permissionsService = this.permissionsService
if ( if (
permissionsService.currentUserCan( permissionsService.currentUserCan(
@@ -102,7 +102,9 @@ export class AppFrameComponent
PermissionType.SavedView PermissionType.SavedView
) )
) { ) {
this.savedViewService.reload() this.savedViewService.reload(() => {
this.savedViewService.maybeRefreshDocumentCounts()
})
} }
} }
@@ -142,6 +144,10 @@ export class AppFrameComponent
}, 200) // slightly longer than css animation for slim sidebar }, 200) // slightly longer than css animation for slim sidebar
} }
get versionString(): string {
return `${environment.appTitle} v${this.settingsService.get(SETTINGS_KEYS.VERSION)}${environment.production ? '' : ` #${environment.tag}`}`
}
get customAppTitle(): string { get customAppTitle(): string {
return this.settingsService.get(SETTINGS_KEYS.APP_TITLE) return this.settingsService.get(SETTINGS_KEYS.APP_TITLE)
} }
@@ -279,4 +285,8 @@ export class AppFrameComponent
onLogout() { onLogout() {
this.openDocumentsService.closeAll() this.openDocumentsService.closeAll()
} }
get showSidebarCounts(): boolean {
return this.settingsService.get(SETTINGS_KEYS.SIDEBAR_VIEWS_SHOW_COUNT)
}
} }

View File

@@ -529,6 +529,17 @@ describe('GlobalSearchComponent', () => {
expect(dispatchSpy).toHaveBeenCalledTimes(2) // once for keydown, second for click expect(dispatchSpy).toHaveBeenCalledTimes(2) // once for keydown, second for click
}) })
it('should support using base href in navigateOrOpenInNewWindow', () => {
jest
.spyOn(component['locationStrategy'], 'getBaseHref')
.mockReturnValue('/base/')
const openSpy = jest.spyOn(window, 'open')
const event = new Event('click')
event['ctrlKey'] = true
component.primaryAction(DataType.Document, { id: 1 }, event as any)
expect(openSpy).toHaveBeenCalledWith('/base/documents/1', '_blank')
})
it('should support title content search and advanced search', () => { it('should support title content search and advanced search', () => {
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
component.query = 'test' component.query = 'test'

View File

@@ -1,4 +1,4 @@
import { NgTemplateOutlet } from '@angular/common' import { LocationStrategy, NgTemplateOutlet } from '@angular/common'
import { import {
Component, Component,
ElementRef, ElementRef,
@@ -6,6 +6,7 @@ import {
QueryList, QueryList,
ViewChild, ViewChild,
ViewChildren, ViewChildren,
inject,
} from '@angular/core' } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router' import { Router } from '@angular/router'
@@ -69,6 +70,17 @@ import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-e
], ],
}) })
export class GlobalSearchComponent implements OnInit { export class GlobalSearchComponent implements OnInit {
searchService = inject(SearchService)
private router = inject(Router)
private modalService = inject(NgbModal)
private documentService = inject(DocumentService)
private documentListViewService = inject(DocumentListViewService)
private permissionsService = inject(PermissionsService)
private toastService = inject(ToastService)
private hotkeyService = inject(HotKeyService)
private settingsService = inject(SettingsService)
private locationStrategy = inject(LocationStrategy)
public DataType = DataType public DataType = DataType
public query: string public query: string
public queryDebounce: Subject<string> public queryDebounce: Subject<string>
@@ -90,17 +102,7 @@ export class GlobalSearchComponent implements OnInit {
) )
} }
constructor( constructor() {
public searchService: SearchService,
private router: Router,
private modalService: NgbModal,
private documentService: DocumentService,
private documentListViewService: DocumentListViewService,
private permissionsService: PermissionsService,
private toastService: ToastService,
private hotkeyService: HotKeyService,
private settingsService: SettingsService
) {
this.queryDebounce = new Subject<string>() this.queryDebounce = new Subject<string>()
this.queryDebounce this.queryDebounce
@@ -421,10 +423,13 @@ export class GlobalSearchComponent implements OnInit {
extras: Object = {} extras: Object = {}
) { ) {
if (newWindow) { if (newWindow) {
const url = this.router.serializeUrl( const serializedUrl = this.router.serializeUrl(
this.router.createUrlTree(commands, extras) this.router.createUrlTree(commands, extras)
) )
window.open(url, '_blank') const baseHref = this.locationStrategy.getBaseHref()
const fullUrl =
baseHref.replace(/\/+$/, '') + '/' + serializedUrl.replace(/^\/+/, '')
window.open(fullUrl, '_blank')
} else { } else {
this.router.navigate(commands, extras) this.router.navigate(commands, extras)
} }

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import { import {
NgbDropdownModule, NgbDropdownModule,
NgbProgressbarModule, NgbProgressbarModule,
@@ -20,7 +20,7 @@ import { ToastComponent } from '../../common/toast/toast.component'
], ],
}) })
export class ToastsDropdownComponent implements OnInit, OnDestroy { export class ToastsDropdownComponent implements OnInit, OnDestroy {
constructor(public toastService: ToastService) {} toastService = inject(ToastService)
private subscription: Subscription private subscription: Subscription

View File

@@ -1,5 +1,5 @@
import { DecimalPipe } from '@angular/common' import { DecimalPipe } from '@angular/common'
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, EventEmitter, Input, Output, inject } 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'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
@@ -12,9 +12,7 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
imports: [DecimalPipe, SafeHtmlPipe], imports: [DecimalPipe, SafeHtmlPipe],
}) })
export class ConfirmDialogComponent extends LoadingComponentWithPermissions { export class ConfirmDialogComponent extends LoadingComponentWithPermissions {
constructor(public activeModal: NgbActiveModal) { activeModal = inject(NgbActiveModal)
super()
}
@Output() @Output()
public confirmClicked = new EventEmitter() public confirmClicked = new EventEmitter()

View File

@@ -1,6 +1,5 @@
import { Component, TemplateRef, ViewChild } from '@angular/core' import { Component, TemplateRef, ViewChild, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { import {
PDFDocumentProxy, PDFDocumentProxy,
PdfViewerComponent, PdfViewerComponent,
@@ -17,6 +16,8 @@ import { ConfirmDialogComponent } from '../confirm-dialog.component'
imports: [PdfViewerModule, FormsModule, ReactiveFormsModule, SafeHtmlPipe], imports: [PdfViewerModule, FormsModule, ReactiveFormsModule, SafeHtmlPipe],
}) })
export class DeletePagesConfirmDialogComponent extends ConfirmDialogComponent { export class DeletePagesConfirmDialogComponent extends ConfirmDialogComponent {
private documentService = inject(DocumentService)
public documentID: number public documentID: number
public pages: number[] = [] public pages: number[] = []
public currentPage: number = 1 public currentPage: number = 1
@@ -34,11 +35,8 @@ export class DeletePagesConfirmDialogComponent extends ConfirmDialogComponent {
return this.documentService.getPreviewUrl(this.documentID) return this.documentService.getPreviewUrl(this.documentID)
} }
constructor( constructor() {
activeModal: NgbActiveModal, super()
private documentService: DocumentService
) {
super(activeModal)
} }
public pdfPreviewLoaded(pdf: PDFDocumentProxy) { public pdfPreviewLoaded(pdf: PDFDocumentProxy) {

View File

@@ -3,9 +3,8 @@ import {
DragDropModule, DragDropModule,
moveItemInArray, moveItemInArray,
} from '@angular/cdk/drag-drop' } from '@angular/cdk/drag-drop'
import { Component, OnInit } from '@angular/core' import { Component, OnInit, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { takeUntil } from 'rxjs' import { takeUntil } from 'rxjs'
import { Document } from 'src/app/data/document' import { Document } from 'src/app/data/document'
@@ -28,6 +27,9 @@ export class MergeConfirmDialogComponent
extends ConfirmDialogComponent extends ConfirmDialogComponent
implements OnInit implements OnInit
{ {
private documentService = inject(DocumentService)
private permissionService = inject(PermissionsService)
public documentIDs: number[] = [] public documentIDs: number[] = []
public archiveFallback: boolean = false public archiveFallback: boolean = false
public deleteOriginals: boolean = false public deleteOriginals: boolean = false
@@ -38,12 +40,8 @@ export class MergeConfirmDialogComponent
public metadataDocumentID: number = -1 public metadataDocumentID: number = -1
constructor( constructor() {
activeModal: NgbActiveModal, super()
private documentService: DocumentService,
private permissionService: PermissionsService
) {
super(activeModal)
} }
ngOnInit() { ngOnInit() {

View File

@@ -1,6 +1,5 @@
import { NgStyle } from '@angular/common' import { NgStyle } from '@angular/common'
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe' import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentService } from 'src/app/services/rest/document.service' import { DocumentService } from 'src/app/services/rest/document.service'
@@ -13,6 +12,8 @@ import { ConfirmDialogComponent } from '../confirm-dialog.component'
imports: [NgStyle, NgxBootstrapIconsModule, SafeHtmlPipe], imports: [NgStyle, NgxBootstrapIconsModule, SafeHtmlPipe],
}) })
export class RotateConfirmDialogComponent extends ConfirmDialogComponent { export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
documentService = inject(DocumentService)
public documentID: number public documentID: number
public showPDFNote: boolean = true public showPDFNote: boolean = true
@@ -25,11 +26,8 @@ export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
return degrees return degrees
} }
constructor( constructor() {
activeModal: NgbActiveModal, super()
public documentService: DocumentService
) {
super(activeModal)
} }
rotate(clockwise: boolean = true) { rotate(clockwise: boolean = true) {

View File

@@ -1,6 +1,5 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer' import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Document } from 'src/app/data/document' import { Document } from 'src/app/data/document'
@@ -23,6 +22,9 @@ export class SplitConfirmDialogComponent
extends ConfirmDialogComponent extends ConfirmDialogComponent
implements OnInit implements OnInit
{ {
private documentService = inject(DocumentService)
private permissionService = inject(PermissionsService)
public get pagesString(): string { public get pagesString(): string {
let pagesStr = '' let pagesStr = ''
@@ -62,12 +64,8 @@ export class SplitConfirmDialogComponent
return this.documentService.getPreviewUrl(this.documentID) return this.documentService.getPreviewUrl(this.documentID)
} }
constructor( constructor() {
activeModal: NgbActiveModal, super()
private documentService: DocumentService,
private permissionService: PermissionsService
) {
super(activeModal)
this.confirmButtonEnabled = this.pages.size > 0 this.confirmButtonEnabled = this.pages.size > 0
} }

View File

@@ -1,5 +1,5 @@
import { CurrencyPipe, getLocaleCurrencyCode } from '@angular/common' import { CurrencyPipe, getLocaleCurrencyCode } from '@angular/common'
import { Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core' import { Component, Input, LOCALE_ID, OnInit, inject } from '@angular/core'
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
import { takeUntil } from 'rxjs' import { takeUntil } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field' import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
@@ -20,6 +20,9 @@ export class CustomFieldDisplayComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit implements OnInit
{ {
private customFieldService = inject(CustomFieldsService)
private documentService = inject(DocumentService)
CustomFieldDataType = CustomFieldDataType CustomFieldDataType = CustomFieldDataType
private _document: Document private _document: Document
@@ -63,11 +66,9 @@ export class CustomFieldDisplayComponent
private defaultCurrencyCode: any private defaultCurrencyCode: any
constructor( constructor() {
private customFieldService: CustomFieldsService, const currentLocale = inject(LOCALE_ID)
private documentService: DocumentService,
@Inject(LOCALE_ID) currentLocale: string
) {
super() super()
this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale) this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale)
this.customFieldService.listAll().subscribe((r) => { this.customFieldService.listAll().subscribe((r) => {

View File

@@ -7,6 +7,7 @@ import {
QueryList, QueryList,
ViewChild, ViewChild,
ViewChildren, ViewChildren,
inject,
} from '@angular/core' } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdownModule, NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdownModule, NgbModal } from '@ng-bootstrap/ng-bootstrap'
@@ -37,6 +38,11 @@ import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit
], ],
}) })
export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissions { export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissions {
private customFieldsService = inject(CustomFieldsService)
private modalService = inject(NgbModal)
private toastService = inject(ToastService)
private permissionsService = inject(PermissionsService)
public popperOptions = pngxPopperOptions public popperOptions = pngxPopperOptions
@Input() @Input()
@@ -78,12 +84,7 @@ export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissio
) )
} }
constructor( constructor() {
private customFieldsService: CustomFieldsService,
private modalService: NgbModal,
private toastService: ToastService,
private permissionsService: PermissionsService
) {
super() super()
this.getFields() this.getFields()
} }

View File

@@ -2,6 +2,7 @@ import { NgTemplateOutlet } from '@angular/common'
import { import {
Component, Component,
EventEmitter, EventEmitter,
inject,
Input, Input,
Output, Output,
QueryList, QueryList,
@@ -178,6 +179,8 @@ export class CustomFieldQueriesModel {
], ],
}) })
export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPermissions { export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPermissions {
protected customFieldsService = inject(CustomFieldsService)
public CustomFieldQueryComponentType = CustomFieldQueryElementType public CustomFieldQueryComponentType = CustomFieldQueryElementType
public CustomFieldQueryOperator = CustomFieldQueryOperator public CustomFieldQueryOperator = CustomFieldQueryOperator
public CustomFieldDataType = CustomFieldDataType public CustomFieldDataType = CustomFieldDataType
@@ -245,7 +248,7 @@ export class CustomFieldsQueryDropdownComponent extends LoadingComponentWithPerm
public readonly today: string = new Date().toISOString().split('T')[0] public readonly today: string = new Date().toISOString().split('T')[0]
constructor(protected customFieldsService: CustomFieldsService) { constructor() {
super() super()
this.selectionModel = new CustomFieldQueriesModel() this.selectionModel = new CustomFieldQueriesModel()
this.getFields() this.getFields()

View File

@@ -6,6 +6,7 @@ import {
OnDestroy, OnDestroy,
OnInit, OnInit,
Output, Output,
inject,
} from '@angular/core' } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { import {
@@ -63,7 +64,9 @@ export enum RelativeDate {
export class DatesDropdownComponent implements OnInit, OnDestroy { export class DatesDropdownComponent implements OnInit, OnDestroy {
public popperOptions = pngxPopperOptions public popperOptions = pngxPopperOptions
constructor(settings: SettingsService) { constructor() {
const settings = inject(SettingsService)
this.datePlaceHolder = settings.getLocalizedDateInputFormat() this.datePlaceHolder = settings.getLocalizedDateInputFormat()
} }

View File

@@ -13,8 +13,6 @@
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select> <pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
@if (patternRequired) { @if (patternRequired) {
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text> <pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
}
@if (patternRequired) {
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></pngx-input-check> <pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive" novalidate></pngx-input-check>
} }

View File

@@ -1,11 +1,10 @@
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
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 { Correspondent } from 'src/app/data/correspondent' import { Correspondent } from 'src/app/data/correspondent'
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
@@ -13,6 +12,7 @@ import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service' import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { CheckComponent } from '../../input/check/check.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component' import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component' import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component' import { TextComponent } from '../../input/text/text.component'
@@ -22,6 +22,7 @@ import { TextComponent } from '../../input/text/text.component'
templateUrl: './correspondent-edit-dialog.component.html', templateUrl: './correspondent-edit-dialog.component.html',
styleUrls: ['./correspondent-edit-dialog.component.scss'], styleUrls: ['./correspondent-edit-dialog.component.scss'],
imports: [ imports: [
CheckComponent,
SelectComponent, SelectComponent,
PermissionsFormComponent, PermissionsFormComponent,
TextComponent, TextComponent,
@@ -31,13 +32,11 @@ import { TextComponent } from '../../input/text/text.component'
], ],
}) })
export class CorrespondentEditDialogComponent extends EditDialogComponent<Correspondent> { export class CorrespondentEditDialogComponent extends EditDialogComponent<Correspondent> {
constructor( constructor() {
service: CorrespondentService, super()
activeModal: NgbActiveModal, this.service = inject(CorrespondentService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
} }
getCreateTitle() { getCreateTitle() {

View File

@@ -5,6 +5,7 @@ import {
OnInit, OnInit,
QueryList, QueryList,
ViewChildren, ViewChildren,
inject,
} from '@angular/core' } from '@angular/core'
import { import {
FormArray, FormArray,
@@ -13,7 +14,6 @@ import {
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { takeUntil } from 'rxjs' import { takeUntil } from 'rxjs'
import { import {
@@ -54,13 +54,11 @@ export class CustomFieldEditDialogComponent
.select_options as FormArray .select_options as FormArray
} }
constructor( constructor() {
service: CustomFieldsService, super()
activeModal: NgbActiveModal, this.service = inject(CustomFieldsService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
} }
ngOnInit(): void { ngOnInit(): void {

View File

@@ -14,8 +14,6 @@
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select> <pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
@if (patternRequired) { @if (patternRequired) {
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text> <pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
}
@if (patternRequired) {
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check> <pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
} }
</div> </div>

View File

@@ -1,11 +1,10 @@
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
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 { DocumentType } from 'src/app/data/document-type' import { DocumentType } from 'src/app/data/document-type'
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
@@ -13,6 +12,7 @@ import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service' import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { CheckComponent } from '../../input/check/check.component'
import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component' import { PermissionsFormComponent } from '../../input/permissions/permissions-form/permissions-form.component'
import { SelectComponent } from '../../input/select/select.component' import { SelectComponent } from '../../input/select/select.component'
import { TextComponent } from '../../input/text/text.component' import { TextComponent } from '../../input/text/text.component'
@@ -22,6 +22,7 @@ import { TextComponent } from '../../input/text/text.component'
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'],
imports: [ imports: [
CheckComponent,
SelectComponent, SelectComponent,
PermissionsFormComponent, PermissionsFormComponent,
TextComponent, TextComponent,
@@ -31,13 +32,11 @@ import { TextComponent } from '../../input/text/text.component'
], ],
}) })
export class DocumentTypeEditDialogComponent extends EditDialogComponent<DocumentType> { export class DocumentTypeEditDialogComponent extends EditDialogComponent<DocumentType> {
constructor( constructor() {
service: DocumentTypeService, super()
activeModal: NgbActiveModal, this.service = inject(DocumentTypeService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
} }
getCreateTitle() { getCreateTitle() {

View File

@@ -41,13 +41,9 @@ import { EditDialogComponent, EditDialogMode } from './edit-dialog.component'
imports: [FormsModule, ReactiveFormsModule], imports: [FormsModule, ReactiveFormsModule],
}) })
class TestComponent extends EditDialogComponent<Tag> { class TestComponent extends EditDialogComponent<Tag> {
constructor( constructor() {
service: TagService, super()
activeModal: NgbActiveModal, this.service = TestBed.inject(TagService)
userService: UserService,
settingsService: SettingsService
) {
super(service, activeModal, userService, settingsService)
} }
getForm(): FormGroup<any> { getForm(): FormGroup<any> {

View File

@@ -1,4 +1,11 @@
import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core' import {
Directive,
EventEmitter,
Input,
OnInit,
Output,
inject,
} 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'
@@ -29,14 +36,12 @@ export abstract class EditDialogComponent<
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit implements OnInit
{ {
constructor( protected service = inject<AbstractPaperlessService<T>>(
protected service: AbstractPaperlessService<T>, AbstractPaperlessService
private activeModal: NgbActiveModal, )
private userService: UserService, protected activeModal = inject(NgbActiveModal)
protected settingsService: SettingsService protected userService = inject(UserService)
) { protected settingsService = inject(SettingsService)
super()
}
users: User[] users: User[]

View File

@@ -1,11 +1,10 @@
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
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 { Group } from 'src/app/data/group' import { Group } from 'src/app/data/group'
import { GroupService } from 'src/app/services/rest/group.service' import { GroupService } from 'src/app/services/rest/group.service'
@@ -26,13 +25,11 @@ import { PermissionsSelectComponent } from '../../permissions-select/permissions
], ],
}) })
export class GroupEditDialogComponent extends EditDialogComponent<Group> { export class GroupEditDialogComponent extends EditDialogComponent<Group> {
constructor( constructor() {
service: GroupService, super()
activeModal: NgbActiveModal, this.service = inject(GroupService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
} }
getCreateTitle() { getCreateTitle() {

View File

@@ -1,15 +1,11 @@
import { Component, ViewChild } from '@angular/core' import { Component, ViewChild, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { import { NgbAlert, NgbAlertModule } from '@ng-bootstrap/ng-bootstrap'
NgbActiveModal,
NgbAlert,
NgbAlertModule,
} 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 { IMAPSecurity, MailAccount } from 'src/app/data/mail-account' import { IMAPSecurity, MailAccount } from 'src/app/data/mail-account'
import { MailAccountService } from 'src/app/services/rest/mail-account.service' import { MailAccountService } from 'src/app/services/rest/mail-account.service'
@@ -47,13 +43,11 @@ export class MailAccountEditDialogComponent extends EditDialogComponent<MailAcco
@ViewChild('testResultAlert', { static: false }) testResultAlert: NgbAlert @ViewChild('testResultAlert', { static: false }) testResultAlert: NgbAlert
constructor( constructor() {
service: MailAccountService, super()
activeModal: NgbActiveModal, this.service = inject(MailAccountService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
} }
getCreateTitle() { getCreateTitle() {

View File

@@ -1,11 +1,10 @@
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { first } from 'rxjs' import { first } from 'rxjs'
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 { Correspondent } from 'src/app/data/correspondent' import { Correspondent } from 'src/app/data/correspondent'
@@ -155,32 +154,34 @@ const METADATA_CORRESPONDENT_OPTIONS = [
], ],
}) })
export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> { export class MailRuleEditDialogComponent extends EditDialogComponent<MailRule> {
private accountService: MailAccountService
private correspondentService: CorrespondentService
private documentTypeService: DocumentTypeService
accounts: MailAccount[] accounts: MailAccount[]
correspondents: Correspondent[] correspondents: Correspondent[]
documentTypes: DocumentType[] documentTypes: DocumentType[]
constructor( constructor() {
service: MailRuleService, super()
activeModal: NgbActiveModal, this.service = inject(MailRuleService)
accountService: MailAccountService, this.accountService = inject(MailAccountService)
correspondentService: CorrespondentService, this.correspondentService = inject(CorrespondentService)
documentTypeService: DocumentTypeService, this.documentTypeService = inject(DocumentTypeService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
accountService this.accountService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.accounts = result.results)) .subscribe((result) => (this.accounts = result.results))
correspondentService this.correspondentService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.correspondents = result.results)) .subscribe((result) => (this.correspondents = result.results))
documentTypeService this.documentTypeService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.documentTypes = result.results)) .subscribe((result) => (this.documentTypes = result.results))

View File

@@ -64,8 +64,6 @@
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select> <pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
@if (patternRequired) { @if (patternRequired) {
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text> <pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
}
@if (patternRequired) {
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check> <pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
} }

View File

@@ -1,12 +1,12 @@
import { AsyncPipe, NgTemplateOutlet } from '@angular/common' import { AsyncPipe, NgTemplateOutlet } from '@angular/common'
import { Component, OnDestroy } from '@angular/core' import { Component, OnDestroy, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbAccordionModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectComponent } from '@ng-select/ng-select' import { NgSelectComponent } from '@ng-select/ng-select'
import { import {
Observable, Observable,
@@ -60,6 +60,8 @@ export class StoragePathEditDialogComponent
extends EditDialogComponent<StoragePath> extends EditDialogComponent<StoragePath>
implements OnDestroy implements OnDestroy
{ {
private documentsService = inject(DocumentService)
public documentsInput$ = new Subject<string>() public documentsInput$ = new Subject<string>()
public foundDocuments$: Observable<Document[]> public foundDocuments$: Observable<Document[]>
private testDocument: Document private testDocument: Document
@@ -68,14 +70,11 @@ export class StoragePathEditDialogComponent
public loading = false public loading = false
public testLoading = false public testLoading = false
constructor( constructor() {
service: StoragePathService, super()
activeModal: NgbActiveModal, this.service = inject(StoragePathService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService, this.settingsService = inject(SettingsService)
private documentsService: DocumentService
) {
super(service, activeModal, userService, settingsService)
this.initPathObservables() this.initPathObservables()
} }

View File

@@ -16,8 +16,6 @@
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select> <pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
@if (patternRequired) { @if (patternRequired) {
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text> <pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
}
@if (patternRequired) {
<pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check> <pngx-input-check i18n-title title="Case insensitive" formControlName="is_insensitive"></pngx-input-check>
} }

View File

@@ -1,11 +1,10 @@
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
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 { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model' import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
import { Tag } from 'src/app/data/tag' import { Tag } from 'src/app/data/tag'
@@ -36,13 +35,11 @@ import { TextComponent } from '../../input/text/text.component'
], ],
}) })
export class TagEditDialogComponent extends EditDialogComponent<Tag> { export class TagEditDialogComponent extends EditDialogComponent<Tag> {
constructor( constructor() {
service: TagService, super()
activeModal: NgbActiveModal, this.service = inject(TagService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService this.settingsService = inject(SettingsService)
) {
super(service, activeModal, userService, settingsService)
} }
getCreateTitle() { getCreateTitle() {

View File

@@ -1,11 +1,10 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { first } from 'rxjs' import { first } from 'rxjs'
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 { Group } from 'src/app/data/group' import { Group } from 'src/app/data/group'
@@ -37,21 +36,21 @@ export class UserEditDialogComponent
extends EditDialogComponent<User> extends EditDialogComponent<User>
implements OnInit implements OnInit
{ {
private toastService = inject(ToastService)
private permissionsService = inject(PermissionsService)
private groupsService: GroupService
groups: Group[] groups: Group[]
passwordIsSet: boolean = false passwordIsSet: boolean = false
public totpLoading: boolean = false public totpLoading: boolean = false
constructor( constructor() {
service: UserService, super()
activeModal: NgbActiveModal, this.service = inject(UserService)
groupsService: GroupService, this.groupsService = inject(GroupService)
settingsService: SettingsService, this.settingsService = inject(SettingsService)
private toastService: ToastService,
private permissionsService: PermissionsService
) {
super(service, activeModal, service, settingsService)
groupsService this.groupsService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.groups = result.results)) .subscribe((result) => (this.groups = result.results))

View File

@@ -129,7 +129,7 @@
formControlName="schedule_offset_days" formControlName="schedule_offset_days"
[showAdd]="false" [showAdd]="false"
[error]="error?.schedule_offset_days" [error]="error?.schedule_offset_days"
hint="Positive values will trigger the workflow before the date, negative values after." hint="Positive values will trigger after the date, negative values before."
i18n-hint i18n-hint
></pngx-input-number> ></pngx-input-number>
</div> </div>

View File

@@ -4,7 +4,7 @@ import {
moveItemInArray, moveItemInArray,
} from '@angular/cdk/drag-drop' } from '@angular/cdk/drag-drop'
import { NgTemplateOutlet } from '@angular/common' import { NgTemplateOutlet } from '@angular/common'
import { Component, OnInit } from '@angular/core' import { Component, OnInit, inject } from '@angular/core'
import { import {
FormArray, FormArray,
FormControl, FormControl,
@@ -12,7 +12,7 @@ import {
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
} from '@angular/forms' } from '@angular/forms'
import { NgbAccordionModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first } from 'rxjs' import { first } from 'rxjs'
import { Correspondent } from 'src/app/data/correspondent' import { Correspondent } from 'src/app/data/correspondent'
@@ -171,6 +171,12 @@ export class WorkflowEditDialogComponent
public WorkflowTriggerType = WorkflowTriggerType public WorkflowTriggerType = WorkflowTriggerType
public WorkflowActionType = WorkflowActionType public WorkflowActionType = WorkflowActionType
private correspondentService: CorrespondentService
private documentTypeService: DocumentTypeService
private storagePathService: StoragePathService
private mailRuleService: MailRuleService
private customFieldsService: CustomFieldsService
templates: Workflow[] templates: Workflow[]
correspondents: Correspondent[] correspondents: Correspondent[]
documentTypes: DocumentType[] documentTypes: DocumentType[]
@@ -183,40 +189,38 @@ export class WorkflowEditDialogComponent
private allowedActionTypes = [] private allowedActionTypes = []
constructor( constructor() {
service: WorkflowService, super()
activeModal: NgbActiveModal, this.service = inject(WorkflowService)
correspondentService: CorrespondentService, this.correspondentService = inject(CorrespondentService)
documentTypeService: DocumentTypeService, this.documentTypeService = inject(DocumentTypeService)
storagePathService: StoragePathService, this.storagePathService = inject(StoragePathService)
mailRuleService: MailRuleService, this.mailRuleService = inject(MailRuleService)
userService: UserService, this.userService = inject(UserService)
settingsService: SettingsService, this.settingsService = inject(SettingsService)
customFieldsService: CustomFieldsService this.customFieldsService = inject(CustomFieldsService)
) {
super(service, activeModal, userService, settingsService)
correspondentService this.correspondentService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.correspondents = result.results)) .subscribe((result) => (this.correspondents = result.results))
documentTypeService this.documentTypeService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.documentTypes = result.results)) .subscribe((result) => (this.documentTypes = result.results))
storagePathService this.storagePathService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.storagePaths = result.results)) .subscribe((result) => (this.storagePaths = result.results))
mailRuleService this.mailRuleService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.mailRules = result.results)) .subscribe((result) => (this.mailRules = result.results))
customFieldsService this.customFieldsService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => { .subscribe((result) => {

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core' import { Component, Input, inject } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
@@ -13,6 +13,10 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
imports: [FormsModule, NgxBootstrapIconsModule], imports: [FormsModule, NgxBootstrapIconsModule],
}) })
export class EmailDocumentDialogComponent extends LoadingComponentWithPermissions { export class EmailDocumentDialogComponent extends LoadingComponentWithPermissions {
private activeModal = inject(NgbActiveModal)
private documentService = inject(DocumentService)
private toastService = inject(ToastService)
@Input() @Input()
title = $localize`Email Document` title = $localize`Email Document`
@@ -37,11 +41,7 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
public emailSubject: string = '' public emailSubject: string = ''
public emailMessage: string = '' public emailMessage: string = ''
constructor( constructor() {
private activeModal: NgbActiveModal,
private documentService: DocumentService,
private toastService: ToastService
) {
super() super()
this.loading = false this.loading = false
} }

View File

@@ -7,6 +7,7 @@ import {
OnInit, OnInit,
Output, Output,
ViewChild, ViewChild,
inject,
} from '@angular/core' } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
@@ -434,6 +435,9 @@ export class FilterableDropdownComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit implements OnInit
{ {
private filterPipe = inject(FilterPipe)
private hotkeyService = inject(HotKeyService)
@ViewChild('listFilterTextInput') listFilterTextInput: ElementRef @ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
@ViewChild('dropdown') dropdown: NgbDropdown @ViewChild('dropdown') dropdown: NgbDropdown
@ViewChild('buttonItems') buttonItems: ElementRef @ViewChild('buttonItems') buttonItems: ElementRef
@@ -536,10 +540,7 @@ export class FilterableDropdownComponent
private keyboardIndex: number private keyboardIndex: number
constructor( constructor() {
private filterPipe: FilterPipe,
private hotkeyService: HotKeyService
) {
super() super()
this.selectionModelChange.subscribe((updatedModel) => { this.selectionModelChange.subscribe((updatedModel) => {
this.modelIsDirty = updatedModel.isDirty() this.modelIsDirty = updatedModel.isDirty()

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
const SYMBOLS = { const SYMBOLS = {
@@ -19,11 +19,11 @@ const SYMBOLS = {
styleUrl: './hotkey-dialog.component.scss', styleUrl: './hotkey-dialog.component.scss',
}) })
export class HotkeyDialogComponent { export class HotkeyDialogComponent {
activeModal = inject(NgbActiveModal)
public title: string = $localize`Keyboard shortcuts` public title: string = $localize`Keyboard shortcuts`
public hotkeys: Map<string, string> = new Map() public hotkeys: Map<string, string> = new Map()
constructor(public activeModal: NgbActiveModal) {}
public close(): void { public close(): void {
this.activeModal.close() this.activeModal.close()
} }

View File

@@ -2,6 +2,7 @@ import {
Component, Component,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
inject,
Input, Input,
Output, Output,
} from '@angular/core' } from '@angular/core'
@@ -55,7 +56,9 @@ import { UrlComponent } from '../url/url.component'
export class CustomFieldsValuesComponent extends AbstractInputComponent<Object> { export class CustomFieldsValuesComponent extends AbstractInputComponent<Object> {
public CustomFieldDataType = CustomFieldDataType public CustomFieldDataType = CustomFieldDataType
constructor(customFieldsService: CustomFieldsService) { constructor() {
const customFieldsService = inject(CustomFieldsService)
super() super()
customFieldsService.listAll().subscribe((items) => { customFieldsService.listAll().subscribe((items) => {
this.fields = items.results this.fields = items.results

View File

@@ -2,6 +2,7 @@ import {
Component, Component,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
inject,
Input, Input,
OnInit, OnInit,
Output, Output,
@@ -45,13 +46,9 @@ export class DateComponent
extends AbstractInputComponent<string> extends AbstractInputComponent<string>
implements OnInit implements OnInit
{ {
constructor( private settings = inject(SettingsService)
private settings: SettingsService, private ngbDateParserFormatter = inject(NgbDateParserFormatter)
private ngbDateParserFormatter: NgbDateParserFormatter, private isoDateAdapter = inject<NgbDateAdapter<string>>(NgbDateAdapter)
private isoDateAdapter: NgbDateAdapter<string>
) {
super()
}
@Input() @Input()
suggestions: string[] suggestions: string[]

View File

@@ -1,5 +1,12 @@
import { AsyncPipe, NgTemplateOutlet } from '@angular/common' import { AsyncPipe, NgTemplateOutlet } from '@angular/common'
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core' import {
Component,
forwardRef,
inject,
Input,
OnDestroy,
OnInit,
} from '@angular/core'
import { import {
FormsModule, FormsModule,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
@@ -52,6 +59,8 @@ export class DocumentLinkComponent
extends AbstractInputComponent<any[]> extends AbstractInputComponent<any[]>
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
private documentsService = inject(DocumentService)
documentsInput$ = new Subject<string>() documentsInput$ = new Subject<string>()
foundDocuments$: Observable<Document[]> foundDocuments$: Observable<Document[]>
loading = false loading = false
@@ -75,10 +84,6 @@ export class DocumentLinkComponent
return this.selectedDocuments.map((d) => d.id) return this.selectedDocuments.map((d) => d.id)
} }
constructor(private documentsService: DocumentService) {
super()
}
ngOnInit() { ngOnInit() {
this.loadDocs() this.loadDocs()
} }

View File

@@ -1,5 +1,6 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing' import { provideHttpClientTesting } from '@angular/common/http/testing'
import { LOCALE_ID } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing' import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NG_VALUE_ACCESSOR } from '@angular/forms' import { NG_VALUE_ACCESSOR } from '@angular/forms'
import { MonetaryComponent } from './monetary.component' import { MonetaryComponent } from './monetary.component'
@@ -41,8 +42,6 @@ describe('MonetaryComponent', () => {
it('should set the default currency code based on LOCALE_ID', () => { it('should set the default currency code based on LOCALE_ID', () => {
expect(component.defaultCurrencyCode).toEqual('USD') // default expect(component.defaultCurrencyCode).toEqual('USD') // default
component = new MonetaryComponent('pt-BR')
expect(component.defaultCurrencyCode).toEqual('BRL')
}) })
it('should support setting a default currency code', () => { it('should support setting a default currency code', () => {
@@ -87,3 +86,28 @@ describe('MonetaryComponent', () => {
expect(component.value).toEqual('USD0.00') expect(component.value).toEqual('USD0.00')
}) })
}) })
describe('MonetaryComponent (Alternate Locale)', () => {
let component: MonetaryComponent
let fixture: ComponentFixture<MonetaryComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MonetaryComponent],
providers: [
{ provide: LOCALE_ID, useValue: 'pt-BR' }, // Brazilian Portuguese
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
}).compileComponents()
fixture = TestBed.createComponent(MonetaryComponent)
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should set the default currency code based on LOCALE_ID', () => {
expect(component.defaultCurrencyCode).toEqual('BRL')
})
})

View File

@@ -1,5 +1,5 @@
import { CurrencyPipe, getLocaleCurrencyCode } from '@angular/common' import { CurrencyPipe, getLocaleCurrencyCode } from '@angular/common'
import { Component, forwardRef, Inject, Input, LOCALE_ID } from '@angular/core' import { Component, forwardRef, inject, Input, LOCALE_ID } from '@angular/core'
import { import {
FormsModule, FormsModule,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
@@ -27,6 +27,8 @@ import { AbstractInputComponent } from '../abstract-input'
], ],
}) })
export class MonetaryComponent extends AbstractInputComponent<string> { export class MonetaryComponent extends AbstractInputComponent<string> {
currentLocale = inject(LOCALE_ID)
public currency: string = '' public currency: string = ''
public _monetaryValue: string = '' public _monetaryValue: string = ''
@@ -45,11 +47,10 @@ export class MonetaryComponent extends AbstractInputComponent<string> {
if (currency) this.defaultCurrencyCode = currency if (currency) this.defaultCurrencyCode = currency
} }
constructor(@Inject(LOCALE_ID) currentLocale: string) { constructor() {
super() super()
this.currency = this.defaultCurrencyCode = this.currency = this.defaultCurrencyCode =
this.defaultCurrency ?? getLocaleCurrencyCode(currentLocale) this.defaultCurrency ?? getLocaleCurrencyCode(this.currentLocale)
} }
writeValue(newValue: any): void { writeValue(newValue: any): void {

View File

@@ -1,4 +1,4 @@
import { Component, forwardRef, Input } from '@angular/core' import { Component, forwardRef, inject, Input } from '@angular/core'
import { import {
FormsModule, FormsModule,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
@@ -22,16 +22,14 @@ import { AbstractInputComponent } from '../abstract-input'
imports: [FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule], imports: [FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule],
}) })
export class NumberComponent extends AbstractInputComponent<number> { export class NumberComponent extends AbstractInputComponent<number> {
private documentService = inject(DocumentService)
@Input() @Input()
showAdd: boolean = true showAdd: boolean = true
@Input() @Input()
step: number = 1 step: number = 1
constructor(private documentService: DocumentService) {
super()
}
nextAsn() { nextAsn() {
if (this.value) { if (this.value) {
return return

View File

@@ -1,4 +1,4 @@
import { Component, forwardRef } from '@angular/core' import { Component, forwardRef, inject } from '@angular/core'
import { import {
FormsModule, FormsModule,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
@@ -26,7 +26,9 @@ import { AbstractInputComponent } from '../../abstract-input'
export class PermissionsGroupComponent extends AbstractInputComponent<Group> { export class PermissionsGroupComponent extends AbstractInputComponent<Group> {
groups: Group[] groups: Group[]
constructor(groupService: GroupService) { constructor() {
const groupService = inject(GroupService)
super() super()
groupService groupService
.listAll() .listAll()

View File

@@ -1,4 +1,4 @@
import { Component, forwardRef } from '@angular/core' import { Component, forwardRef, inject } from '@angular/core'
import { import {
FormsModule, FormsModule,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
@@ -8,7 +8,6 @@ import { NgSelectComponent } from '@ng-select/ng-select'
import { first } from 'rxjs/operators' import { first } from 'rxjs/operators'
import { User } from 'src/app/data/user' import { User } from 'src/app/data/user'
import { UserService } from 'src/app/services/rest/user.service' import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { AbstractInputComponent } from '../../abstract-input' import { AbstractInputComponent } from '../../abstract-input'
@Component({ @Component({
@@ -27,7 +26,9 @@ import { AbstractInputComponent } from '../../abstract-input'
export class PermissionsUserComponent extends AbstractInputComponent<User[]> { export class PermissionsUserComponent extends AbstractInputComponent<User[]> {
users: User[] users: User[]
constructor(userService: UserService, settings: SettingsService) { constructor() {
const userService = inject(UserService)
super() super()
userService userService
.listAll() .listAll()

View File

@@ -2,6 +2,7 @@ import {
Component, Component,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
inject,
Input, Input,
OnInit, OnInit,
Output, Output,
@@ -45,10 +46,10 @@ import { TagComponent } from '../../tag/tag.component'
], ],
}) })
export class TagsComponent implements OnInit, ControlValueAccessor { export class TagsComponent implements OnInit, ControlValueAccessor {
constructor( private tagService = inject(TagService)
private tagService: TagService, private modalService = inject(NgbModal)
private modalService: NgbModal
) { constructor() {
this.createTagRef = this.createTag.bind(this) this.createTagRef = this.createTag.bind(this)
} }

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core' import { Component, Input, inject } from '@angular/core'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { SettingsService } from 'src/app/services/settings.service' import { SettingsService } from 'src/app/services/settings.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
@@ -9,6 +9,8 @@ import { environment } from 'src/environments/environment'
styleUrls: ['./logo.component.scss'], styleUrls: ['./logo.component.scss'],
}) })
export class LogoComponent { export class LogoComponent {
private settingsService = inject(SettingsService)
@Input() @Input()
extra_classes: string extra_classes: string
@@ -24,8 +26,6 @@ export class LogoComponent {
: null : null
} }
constructor(private settingsService: SettingsService) {}
getClasses() { getClasses() {
return ['logo'].concat(this.extra_classes).join(' ') return ['logo'].concat(this.extra_classes).join(' ')
} }

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core' import { Component, Input, inject } from '@angular/core'
import { Title } from '@angular/platform-browser' import { Title } from '@angular/platform-browser'
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap' import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
@@ -12,7 +12,7 @@ import { environment } from 'src/environments/environment'
imports: [NgbPopoverModule, NgxBootstrapIconsModule, TourNgBootstrapModule], imports: [NgbPopoverModule, NgxBootstrapIconsModule, TourNgBootstrapModule],
}) })
export class PageHeaderComponent { export class PageHeaderComponent {
constructor(private titleService: Title) {} private titleService = inject(Title)
_title = '' _title = ''

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
@@ -24,13 +24,13 @@ import { SwitchComponent } from '../input/switch/switch.component'
], ],
}) })
export class PermissionsDialogComponent { export class PermissionsDialogComponent {
activeModal = inject(NgbActiveModal)
private userService = inject(UserService)
users: User[] users: User[]
private o: ObjectWithPermissions = undefined private o: ObjectWithPermissions = undefined
constructor( constructor() {
public activeModal: NgbActiveModal,
private userService: UserService
) {
this.userService.listAll().subscribe((r) => (this.users = r.results)) this.userService.listAll().subscribe((r) => (this.users = r.results))
} }

View File

@@ -1,5 +1,5 @@
import { NgClass } from '@angular/common' import { NgClass } from '@angular/common'
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectComponent } from '@ng-select/ng-select' import { NgSelectComponent } from '@ng-select/ng-select'
@@ -58,6 +58,9 @@ export enum OwnerFilterType {
], ],
}) })
export class PermissionsFilterDropdownComponent extends ComponentWithPermissions { export class PermissionsFilterDropdownComponent extends ComponentWithPermissions {
permissionsService = inject(PermissionsService)
private settingsService = inject(SettingsService)
public OwnerFilterType = OwnerFilterType public OwnerFilterType = OwnerFilterType
@Input() @Input()
@@ -83,12 +86,12 @@ export class PermissionsFilterDropdownComponent extends ComponentWithPermissions
) )
} }
constructor( constructor() {
public permissionsService: PermissionsService, const userService = inject(UserService)
userService: UserService,
private settingsService: SettingsService
) {
super() super()
const permissionsService = this.permissionsService
if ( if (
permissionsService.currentUserCan( permissionsService.currentUserCan(
PermissionAction.View, PermissionAction.View,

View File

@@ -1,5 +1,5 @@
import { KeyValuePipe } from '@angular/common' import { KeyValuePipe } from '@angular/common'
import { Component, forwardRef, Input, OnInit } from '@angular/core' import { Component, forwardRef, inject, Input, OnInit } from '@angular/core'
import { import {
AbstractControl, AbstractControl,
ControlValueAccessor, ControlValueAccessor,
@@ -43,6 +43,9 @@ export class PermissionsSelectComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, ControlValueAccessor implements OnInit, ControlValueAccessor
{ {
private readonly permissionsService = inject(PermissionsService)
private readonly settingsService = inject(SettingsService)
@Input() @Input()
title: string = 'Permissions' title: string = 'Permissions'
@@ -76,10 +79,7 @@ export class PermissionsSelectComponent
public allowedTypes = Object.keys(PermissionType) public allowedTypes = Object.keys(PermissionType)
constructor( constructor() {
private readonly permissionsService: PermissionsService,
private readonly settingsService: SettingsService
) {
super() super()
if (!this.settingsService.get(SETTINGS_KEYS.AUDITLOG_ENABLED)) { if (!this.settingsService.get(SETTINGS_KEYS.AUDITLOG_ENABLED)) {
this.allowedTypes.splice(this.allowedTypes.indexOf('History'), 1) this.allowedTypes.splice(this.allowedTypes.indexOf('History'), 1)

View File

@@ -1,5 +1,5 @@
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { Component, Input, OnDestroy, ViewChild } from '@angular/core' import { Component, inject, Input, OnDestroy, ViewChild } from '@angular/core'
import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap' import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import { PdfViewerComponent, PdfViewerModule } from 'ng2-pdf-viewer' import { PdfViewerComponent, PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
@@ -24,6 +24,10 @@ import { SettingsService } from 'src/app/services/settings.service'
], ],
}) })
export class PreviewPopupComponent implements OnDestroy { export class PreviewPopupComponent implements OnDestroy {
private settingsService = inject(SettingsService)
private documentService = inject(DocumentService)
private http = inject(HttpClient)
private _document: Document private _document: Document
@Input() @Input()
set document(document: Document) { set document(document: Document) {
@@ -82,12 +86,6 @@ export class PreviewPopupComponent implements OnDestroy {
) )
} }
constructor(
private settingsService: SettingsService,
private documentService: DocumentService,
private http: HttpClient
) {}
ngOnDestroy(): void { ngOnDestroy(): void {
this.unsubscribeNotifier.next(this) this.unsubscribeNotifier.next(this)
} }

View File

@@ -1,5 +1,5 @@
import { Clipboard } from '@angular/cdk/clipboard' import { Clipboard } from '@angular/cdk/clipboard'
import { Component, OnInit } from '@angular/core' import { Component, OnInit, inject } from '@angular/core'
import { import {
FormControl, FormControl,
FormGroup, FormGroup,
@@ -46,6 +46,11 @@ export class ProfileEditDialogComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit implements OnInit
{ {
private profileService = inject(ProfileService)
activeModal = inject(NgbActiveModal)
private toastService = inject(ToastService)
private clipboard = inject(Clipboard)
public networkActive: boolean = false public networkActive: boolean = false
public error: any public error: any
@@ -83,15 +88,6 @@ export class ProfileEditDialogComponent
public socialAccounts: SocialAccount[] = [] public socialAccounts: SocialAccount[] = []
public socialAccountProviders: SocialAccountProvider[] = [] public socialAccountProviders: SocialAccountProvider[] = []
constructor(
private profileService: ProfileService,
public activeModal: NgbActiveModal,
private toastService: ToastService,
private clipboard: Clipboard
) {
super()
}
ngOnInit(): void { ngOnInit(): void {
this.networkActive = true this.networkActive = true
this.profileService this.profileService

View File

@@ -10,6 +10,7 @@ import {
fakeAsync, fakeAsync,
tick, tick,
} from '@angular/core/testing' } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser' import { By } from '@angular/platform-browser'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
@@ -33,6 +34,8 @@ describe('ShareLinksDialogComponent', () => {
imports: [ imports: [
ShareLinksDialogComponent, ShareLinksDialogComponent,
NgxBootstrapIconsModule.pick(allIcons), NgxBootstrapIconsModule.pick(allIcons),
FormsModule,
ReactiveFormsModule,
], ],
providers: [ providers: [
provideHttpClient(withInterceptorsFromDi()), provideHttpClient(withInterceptorsFromDi()),
@@ -223,16 +226,18 @@ describe('ShareLinksDialogComponent', () => {
) )
}) })
it('should disable archive switch & option if no archive available', () => { it('should disable archive switch & option if no archive available', (done) => {
component.hasArchiveVersion = false component.hasArchiveVersion = false
component.ngOnInit() component.ngOnInit()
fixture.detectChanges() fixture.detectChanges()
expect(component.useArchiveVersion).toBeFalsy() expect(component.useArchiveVersion).toBeFalsy()
expect( setTimeout(() => {
fixture.debugElement.query(By.css("input[type='checkbox']")).attributes[ // some stupid change detection issue
'ng-reflect-is-disabled' const inputEl = fixture.debugElement.query(By.css('#versionSwitch'))
] .nativeElement as HTMLInputElement
).toBeTruthy() expect(inputEl.disabled).toBeTruthy()
done()
})
}) })
it('should support close', () => { it('should support close', () => {

View File

@@ -1,5 +1,5 @@
import { Clipboard } from '@angular/cdk/clipboard' import { Clipboard } from '@angular/cdk/clipboard'
import { Component, Input, OnInit } from '@angular/core' import { Component, Input, OnInit, inject } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
@@ -16,6 +16,11 @@ import { environment } from 'src/environments/environment'
imports: [FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule], imports: [FormsModule, ReactiveFormsModule, NgxBootstrapIconsModule],
}) })
export class ShareLinksDialogComponent implements OnInit { export class ShareLinksDialogComponent implements OnInit {
private activeModal = inject(NgbActiveModal)
private shareLinkService = inject(ShareLinkService)
private toastService = inject(ToastService)
private clipboard = inject(Clipboard)
EXPIRATION_OPTIONS = [ EXPIRATION_OPTIONS = [
{ label: $localize`1 day`, value: 1 }, { label: $localize`1 day`, value: 1 },
{ label: $localize`7 days`, value: 7 }, { label: $localize`7 days`, value: 7 },
@@ -58,13 +63,6 @@ export class ShareLinksDialogComponent implements OnInit {
useArchiveVersion: boolean = true useArchiveVersion: boolean = true
constructor(
private activeModal: NgbActiveModal,
private shareLinkService: ShareLinkService,
private toastService: ToastService,
private clipboard: Clipboard
) {}
ngOnInit(): void { ngOnInit(): void {
if (this._documentId !== undefined) this.refresh() if (this._documentId !== undefined) this.refresh()
} }

View File

@@ -20,7 +20,18 @@
<div class="card-body"> <div class="card-body">
<dl class="card-text"> <dl class="card-text">
<dt i18n>Paperless-ngx Version</dt> <dt i18n>Paperless-ngx Version</dt>
<dd>{{status.pngx_version}}</dd> <dd>
{{status.pngx_version}}
@if (versionMismatch) {
<button class="btn btn-sm d-inline align-items-center btn-dark text-uppercase small" [ngbPopover]="versionPopover" triggers="click mouseenter:mouseleave">
<i-bs name="exclamation-triangle-fill" class="text-danger lh-1"></i-bs>
</button>
}
<ng-template #versionPopover>
Frontend version: {{frontendVersion}}<br>
Backend version: {{status.pngx_version}}
</ng-template>
</dd>
<dt i18n>Install Type</dt> <dt i18n>Install Type</dt>
<dd>{{status.install_type}}</dd> <dd>{{status.install_type}}</dd>
<dt i18n>Server OS</dt> <dt i18n>Server OS</dt>

View File

@@ -1,3 +1,18 @@
// Mock production environment for testing
jest.mock('src/environments/environment', () => ({
environment: {
production: true,
apiBaseUrl: 'http://localhost:8000/api/',
apiVersion: '9',
appTitle: 'Paperless-ngx',
tag: 'prod',
version: '2.4.3',
webSocketHost: 'localhost:8000',
webSocketProtocol: 'ws:',
webSocketBaseUrl: '/ws/',
},
}))
import { Clipboard } from '@angular/cdk/clipboard' import { Clipboard } from '@angular/cdk/clipboard'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing' import { provideHttpClientTesting } from '@angular/common/http/testing'
@@ -142,4 +157,15 @@ describe('SystemStatusDialogComponent', () => {
`Task ${PaperlessTaskName.IndexOptimize} started` `Task ${PaperlessTaskName.IndexOptimize} started`
) )
}) })
it('shoduld handle version mismatch', () => {
component.frontendVersion = '2.4.2'
component.ngOnInit()
expect(component.versionMismatch).toBeTruthy()
expect(component.status.pngx_version).toContain('(frontend: 2.4.2)')
component.frontendVersion = '2.4.3'
component.status.pngx_version = '2.4.3'
component.ngOnInit()
expect(component.versionMismatch).toBeFalsy()
})
}) })

View File

@@ -1,5 +1,5 @@
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard' import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
import { Component } from '@angular/core' import { Component, OnInit, inject } from '@angular/core'
import { import {
NgbActiveModal, NgbActiveModal,
NgbModalModule, NgbModalModule,
@@ -18,6 +18,7 @@ import { PermissionsService } from 'src/app/services/permissions.service'
import { SystemStatusService } from 'src/app/services/system-status.service' import { SystemStatusService } from 'src/app/services/system-status.service'
import { TasksService } from 'src/app/services/tasks.service' import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
@Component({ @Component({
selector: 'pngx-system-status-dialog', selector: 'pngx-system-status-dialog',
@@ -33,10 +34,19 @@ import { ToastService } from 'src/app/services/toast.service'
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
], ],
}) })
export class SystemStatusDialogComponent { export class SystemStatusDialogComponent implements OnInit {
activeModal = inject(NgbActiveModal)
private clipboard = inject(Clipboard)
private systemStatusService = inject(SystemStatusService)
private tasksService = inject(TasksService)
private toastService = inject(ToastService)
private permissionsService = inject(PermissionsService)
public SystemStatusItemStatus = SystemStatusItemStatus public SystemStatusItemStatus = SystemStatusItemStatus
public PaperlessTaskName = PaperlessTaskName public PaperlessTaskName = PaperlessTaskName
public status: SystemStatus public status: SystemStatus
public frontendVersion: string = environment.version
public versionMismatch: boolean = false
public copied: boolean = false public copied: boolean = false
@@ -46,14 +56,16 @@ export class SystemStatusDialogComponent {
return this.permissionsService.isSuperUser() return this.permissionsService.isSuperUser()
} }
constructor( public ngOnInit() {
public activeModal: NgbActiveModal, this.versionMismatch =
private clipboard: Clipboard, environment.production &&
private systemStatusService: SystemStatusService, this.status.pngx_version &&
private tasksService: TasksService, this.frontendVersion &&
private toastService: ToastService, this.status.pngx_version !== this.frontendVersion
private permissionsService: PermissionsService if (this.versionMismatch) {
) {} this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})`
}
}
public close() { public close() {
this.activeModal.close() this.activeModal.close()

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core' import { Component, inject, Input } from '@angular/core'
import { Tag } from 'src/app/data/tag' import { Tag } from 'src/app/data/tag'
import { import {
PermissionAction, PermissionAction,
@@ -13,14 +13,12 @@ import { TagService } from 'src/app/services/rest/tag.service'
styleUrls: ['./tag.component.scss'], styleUrls: ['./tag.component.scss'],
}) })
export class TagComponent { export class TagComponent {
private permissionsService = inject(PermissionsService)
private tagService = inject(TagService)
private _tag: Tag private _tag: Tag
private _tagID: number private _tagID: number
constructor(
private permissionsService: PermissionsService,
private tagService: TagService
) {}
@Input() @Input()
public set tag(tag: Tag) { public set tag(tag: Tag) {
this._tag = tag this._tag = tag

View File

@@ -1,6 +1,6 @@
import { Clipboard } from '@angular/cdk/clipboard' import { Clipboard } from '@angular/cdk/clipboard'
import { DecimalPipe } from '@angular/common' import { DecimalPipe } from '@angular/common'
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, EventEmitter, Input, Output, inject } from '@angular/core'
import { import {
NgbProgressbarModule, NgbProgressbarModule,
NgbToastModule, NgbToastModule,
@@ -21,6 +21,8 @@ import { Toast } from 'src/app/services/toast.service'
styleUrl: './toast.component.scss', styleUrl: './toast.component.scss',
}) })
export class ToastComponent { export class ToastComponent {
private clipboard = inject(Clipboard)
@Input() toast: Toast @Input() toast: Toast
@Input() autohide: boolean = true @Input() autohide: boolean = true
@@ -31,8 +33,6 @@ export class ToastComponent {
public copied: boolean = false public copied: boolean = false
constructor(private clipboard: Clipboard) {}
onShown(toast: Toast) { onShown(toast: Toast) {
if (!this.autohide) return if (!this.autohide) return

View File

@@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import { import {
NgbAccordionModule, NgbAccordionModule,
NgbProgressbarModule, NgbProgressbarModule,
@@ -20,7 +20,7 @@ import { ToastComponent } from '../toast/toast.component'
], ],
}) })
export class ToastsComponent implements OnInit, OnDestroy { export class ToastsComponent implements OnInit, OnDestroy {
constructor(public toastService: ToastService) {} toastService = inject(ToastService)
private subscription: Subscription private subscription: Subscription

View File

@@ -5,7 +5,7 @@ import {
DragDropModule, DragDropModule,
moveItemInArray, moveItemInArray,
} from '@angular/cdk/drag-drop' } from '@angular/cdk/drag-drop'
import { Component } from '@angular/core' import { Component, inject } from '@angular/core'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
@@ -42,13 +42,13 @@ import { WelcomeWidgetComponent } from './widgets/welcome-widget/welcome-widget.
], ],
}) })
export class DashboardComponent extends ComponentWithPermissions { export class DashboardComponent extends ComponentWithPermissions {
settingsService = inject(SettingsService)
savedViewService = inject(SavedViewService)
private tourService = inject(TourService)
private toastService = inject(ToastService)
public dashboardViews: SavedView[] = [] public dashboardViews: SavedView[] = []
constructor( constructor() {
public settingsService: SettingsService,
public savedViewService: SavedViewService,
private tourService: TourService,
private toastService: ToastService
) {
super() super()
this.savedViewService.listAll().subscribe(() => { this.savedViewService.listAll().subscribe(() => {

View File

@@ -1,6 +1,7 @@
<pngx-widget-frame <pngx-widget-frame
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
[title]="savedView.name" [title]="savedView.name"
[badge]="count"
[loading]="loading" [loading]="loading"
[draggable]="savedView" [draggable]="savedView"
> >

View File

@@ -1,6 +1,7 @@
import { AsyncPipe, NgClass, NgStyle } from '@angular/common' import { AsyncPipe, NgClass, NgStyle } from '@angular/common'
import { import {
Component, Component,
inject,
Input, Input,
OnDestroy, OnDestroy,
OnInit, OnInit,
@@ -84,26 +85,22 @@ export class SavedViewWidgetComponent
extends LoadingComponentWithPermissions extends LoadingComponentWithPermissions
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
private documentService = inject(DocumentService)
private router = inject(Router)
private list = inject(DocumentListViewService)
private websocketStatusService = inject(WebsocketStatusService)
openDocumentsService = inject(OpenDocumentsService)
documentListViewService = inject(DocumentListViewService)
permissionsService = inject(PermissionsService)
private settingsService = inject(SettingsService)
private customFieldService = inject(CustomFieldsService)
public DisplayMode = DisplayMode public DisplayMode = DisplayMode
public DisplayField = DisplayField public DisplayField = DisplayField
public CustomFieldDataType = CustomFieldDataType public CustomFieldDataType = CustomFieldDataType
private customFields: CustomField[] = [] private customFields: CustomField[] = []
constructor(
private documentService: DocumentService,
private router: Router,
private list: DocumentListViewService,
private websocketStatusService: WebsocketStatusService,
public openDocumentsService: OpenDocumentsService,
public documentListViewService: DocumentListViewService,
public permissionsService: PermissionsService,
private settingsService: SettingsService,
private customFieldService: CustomFieldsService
) {
super()
}
@Input() @Input()
savedView: SavedView savedView: SavedView
@@ -121,6 +118,8 @@ export class SavedViewWidgetComponent
displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS displayFields: DisplayField[] = DEFAULT_DASHBOARD_DISPLAY_FIELDS
count: number
ngOnInit(): void { ngOnInit(): void {
this.reload() this.reload()
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
@@ -181,6 +180,7 @@ export class SavedViewWidgetComponent
tap((result) => { tap((result) => {
this.show = true this.show = true
this.documents = result.results this.documents = result.results
this.count = result.count
}), }),
delay(500) delay(500)
) )

View File

@@ -1,6 +1,6 @@
import { DecimalPipe } from '@angular/common' import { DecimalPipe } from '@angular/common'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, inject, OnDestroy, OnInit } from '@angular/core'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap' import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import * as mimeTypeNames from 'mime-names' import * as mimeTypeNames from 'mime-names'
@@ -51,15 +51,11 @@ export class StatisticsWidgetComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
loading: boolean = false private http = inject(HttpClient)
private websocketConnectionService = inject(WebsocketStatusService)
private documentListViewService = inject(DocumentListViewService)
constructor( loading: boolean = false
private http: HttpClient,
private websocketConnectionService: WebsocketStatusService,
private documentListViewService: DocumentListViewService
) {
super()
}
statistics: Statistics = {} statistics: Statistics = {}

View File

@@ -1,5 +1,5 @@
import { NgClass, NgTemplateOutlet } from '@angular/common' import { NgClass, NgTemplateOutlet } from '@angular/common'
import { Component, QueryList, ViewChildren } from '@angular/core' import { Component, QueryList, ViewChildren, inject } from '@angular/core'
import { RouterModule } from '@angular/router' import { RouterModule } from '@angular/router'
import { import {
NgbAlert, NgbAlert,
@@ -37,15 +37,11 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
], ],
}) })
export class UploadFileWidgetComponent extends ComponentWithPermissions { export class UploadFileWidgetComponent extends ComponentWithPermissions {
@ViewChildren(NgbAlert) alerts: QueryList<NgbAlert> private websocketStatusService = inject(WebsocketStatusService)
private uploadDocumentsService = inject(UploadDocumentsService)
settingsService = inject(SettingsService)
constructor( @ViewChildren(NgbAlert) alerts: QueryList<NgbAlert>
private websocketStatusService: WebsocketStatusService,
private uploadDocumentsService: UploadDocumentsService,
public settingsService: SettingsService
) {
super()
}
getStatus() { getStatus() {
return this.websocketStatusService.getConsumerStatus() return this.websocketStatusService.getConsumerStatus()

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Output } from '@angular/core' import { Component, EventEmitter, Output, inject } from '@angular/core'
import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap' import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap'
import { TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@@ -9,7 +9,7 @@ import { TourService } from 'ngx-ui-tour-ng-bootstrap'
imports: [NgbAlertModule], imports: [NgbAlertModule],
}) })
export class WelcomeWidgetComponent { export class WelcomeWidgetComponent {
constructor(public readonly tourService: TourService) {} readonly tourService = inject(TourService)
@Output() @Output()
dismiss: EventEmitter<boolean> = new EventEmitter() dismiss: EventEmitter<boolean> = new EventEmitter()

View File

@@ -2,13 +2,16 @@
<div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent"> <div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<div class="d-flex"> <div class="d-flex align-items-center">
@if (draggable) { @if (draggable) {
<div class="ms-n2 me-1" cdkDragHandle> <div class="ms-n2 me-1" cdkDragHandle>
<i-bs name="grip-vertical"></i-bs> <i-bs name="grip-vertical"></i-bs>
</div> </div>
} }
<h6 class="card-title mb-0">{{title}}</h6> <h6 class="card-title mb-0">{{title}}</h6>
@if (badge) {
<span class="badge bg-info text-dark ms-2">{{badge}}</span>
}
</div> </div>
@if (loading) { @if (loading) {
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div> <div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>

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