Compare commits

..

259 Commits

Author SHA1 Message Date
shamoon
056b314a89 Update consumer.py 2025-06-26 14:49:08 -07:00
shamoon
ba91bb154a Update consumer.py 2025-06-26 14:41:39 -07:00
shamoon
3c4225979e Update consumer.py 2025-06-26 14:40:29 -07:00
shamoon
84b5ef7c34 Update consumer.py 2025-06-26 14:34:44 -07:00
shamoon
d579caf1ea Enhancement: better try to catch db errors before unlink 2025-06-26 14:24:45 -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
shamoon
00acbd4f1d Bump version to 2.16.2 2025-05-24 10:54:17 -07:00
shamoon
28a1b9d1ac Chore: remove invalid branches-ignores 2025-05-24 10:51:52 -07:00
github-actions[bot]
4f8a931a61 New Crowdin translations by GitHub Action (#10028) 2025-05-24 10:37:58 -07:00
github-actions[bot]
716ebfe08a New Crowdin translations by GitHub Action (#9989)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-05-24 10:19:35 -07:00
GitHub Actions
e1760db85c Auto translate strings 2025-05-24 17:15:50 +00:00
shamoon
3cbb5239fb Fix: accept datetime for created (#10021) 2025-05-24 17:14:15 +00:00
Samuel Kosmann
aed629269d add fallback to copyfile on PermissionError (#10023)
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2025-05-24 16:54:24 +00:00
shamoon
59bf25edb1 Fix: created date fixes in v2.16 (#10026) 2025-05-24 09:45:07 -07:00
GitHub Actions
eb07876657 Auto translate strings 2025-05-24 14:29:39 +00:00
shamoon
ae3ac2b719 Fix: mark fields for created objects as dirty (#10022) 2025-05-24 07:28:04 -07:00
shamoon
5b4b316bbc Chore: warn users about removal of postgres v13 support (#9980) 2025-05-21 18:17:50 +00:00
shamoon
1583783a0b Resolve test timezone warnings 2025-05-21 00:51:53 -07:00
shamoon
bd5f05ff2b Documentation: remove duplicates from changelog 2025-05-19 22:17:56 -07:00
github-actions[bot]
9be6b28141 Changelog v2.16.1 - GHA (#9978)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-19 14:29:13 -07:00
shamoon
3de8c9073d Bump version to 2.16.1 2025-05-19 13:57:41 -07:00
github-actions[bot]
e90a2aa4eb New Crowdin translations by GitHub Action (#9964)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-05-19 13:56:03 -07:00
GitHub Actions
edef851c89 Auto translate strings 2025-05-19 20:53:46 +00:00
shamoon
7b37e037e4 Fix: fix created dater filtering in 2.16.0 (#9976) 2025-05-19 13:52:09 -07:00
shamoon
e092627da6 Chore: improve PR labeling move all labelling to pr-bot workflow (#9970) 2025-05-19 13:44:25 -07:00
shamoon
0a03ca94c7 Fix dev API version 2025-05-19 13:34:22 -07:00
github-actions[bot]
86dec8f344 Documentation: Add v2.16.0 changelog (#9969)
* Changelog v2.16.0 - GHA

* Fix categorizations

---------

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-19 13:01:43 -07:00
shamoon
0b079f57ed Partially Revert "Chore(deps): Bump the small-changes group across 1 directory with 6 updates (#9921)"
This partially reverts commit 1fe166e014.
2025-05-19 12:20:05 -07:00
shamoon
ba80790826 Bump version to 2.16.0 2025-05-19 11:37:27 -07:00
shamoon
2f8b2944f1 Merge branch 'dev' 2025-05-19 11:37:01 -07:00
github-actions[bot]
46ddba4579 New Crowdin translations by GitHub Action (#9724) 2025-05-19 11:06:03 -07:00
dependabot[bot]
1fe166e014 Chore(deps): Bump the small-changes group across 1 directory with 6 updates (#9921)
* Chore(deps): Bump the small-changes group across 1 directory with 6 updates

Bumps the small-changes group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [concurrent-log-handler](https://github.com/Preston-Landers/concurrent-log-handler) | `0.9.25` | `0.9.26` |
| [django](https://github.com/django/django) | `5.1.8` | `5.2.1` |
| [django-auditlog](https://github.com/jazzband/django-auditlog) | `3.0.0` | `3.1.2` |
| [drf-spectacular-sidecar](https://github.com/tfranzel/drf-spectacular-sidecar) | `2025.4.1` | `2025.5.1` |
| [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF) | `16.10.0` | `16.10.1` |
| [setproctitle](https://github.com/dvarrazzo/py-setproctitle) | `1.3.5` | `1.3.6` |



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 `django` from 5.1.8 to 5.2.1
- [Commits](https://github.com/django/django/compare/5.1.8...5.2.1)

Updates `django-auditlog` from 3.0.0 to 3.1.2
- [Release notes](https://github.com/jazzband/django-auditlog/releases)
- [Changelog](https://github.com/jazzband/django-auditlog/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jazzband/django-auditlog/compare/v3.0.0...v3.1.2)

Updates `drf-spectacular-sidecar` from 2025.4.1 to 2025.5.1
- [Commits](https://github.com/tfranzel/drf-spectacular-sidecar/compare/2025.4.1...2025.5.1)

Updates `ocrmypdf` from 16.10.0 to 16.10.1
- [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.1)

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: django
  dependency-version: 5.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: django-auditlog
  dependency-version: 3.1.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: drf-spectacular-sidecar
  dependency-version: 2025.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: ocrmypdf
  dependency-version: 16.10.1
  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>

* Fix log matches related to newlines, add newlines to stdout.writelines

* Fix disable api remote auth test, Django 5.2 no longer uses process_request

---------

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-05-19 11:03:02 -07:00
GitHub Actions
71bc483eec Auto translate strings 2025-05-19 16:41:14 +00:00
shamoon
55917fcabe Fix: handle created change with api version increment, use created only on frontend, deprecate created_date (#9962) 2025-05-19 09:38:01 -07:00
GitHub Actions
fae4116504 Auto translate strings 2025-05-17 05:22:59 +00:00
shamoon
83db0355f3 Chore: automatically disable email verification if no smtp setup (#9949) 2025-05-16 22:21:24 -07:00
shamoon
6647b32ac0 Fix: ignore logo file from sanity checker (#9946) 2025-05-16 17:30:34 -07:00
GitHub Actions
3365fc92fd Auto translate strings 2025-05-16 14:25:26 +00:00
shamoon
1a6f32534c Change: treat created as date not datetime (#9793) 2025-05-16 14:23:04 +00:00
GitHub Actions
ce5d4e9c92 Auto translate strings 2025-05-13 06:07:40 +00:00
shamoon
0ab85b5122 Fix/Chore: replace file drop package (#9926) 2025-05-12 23:05:34 -07:00
shamoon
2c9e690dfb Chore: resolve dynamic import warnings from pdfjs, again (#9924) 2025-05-12 21:04:18 -07:00
shamoon
344cc70cd5 Enhancement: support negative offset in scheduled workflows (#9746) 2025-05-11 20:04:46 +00:00
shamoon
73f0f1212d Fixhancement: better handle removed social apps in profile (#9876) 2025-05-11 19:55:33 +00:00
GitHub Actions
a61f5ac64c Auto translate strings 2025-05-11 19:45:45 +00:00
shamoon
6a5be992c0 Enhancement: add barcode frontend config (#9742) 2025-05-11 19:44:06 +00:00
dependabot[bot]
bcb0ae1ee5 docker-compose(deps): Bump library/redis from 7 to 8 in /docker/compose (#9879)
Bumps library/redis from 7 to 8.

---
updated-dependencies:
- dependency-name: library/redis
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-07 16:49:18 +00:00
dependabot[bot]
6bc385b49b Chore(deps): Bump astral-sh/setup-uv from 5 to 6 in the actions group (#9842)
Bumps the actions group with 1 update: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `astral-sh/setup-uv` from 5 to 6
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v6)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-07 16:22:08 +00:00
shamoon
c6b3fd8bcb Chore: update s6-overlay to v3.2.1.0 (#9893) 2025-05-07 08:56:11 -07:00
shamoon
7cabb5bf98 Documentation: correct custom field search example 2025-05-07 06:41:49 -07:00
shamoon
011056a9d5 Documentation: clarify disable consumer setting 2025-05-03 22:55:29 -07:00
shamoon
384a197c6f Documentation: clarify PAPERLESS_CONSUMER_DISABLE (#9862) 2025-05-03 07:57:06 -07:00
shamoon
0542758641 Chore: split ci frontend e2e vs unit tests (#9851) 2025-05-02 00:38:20 -07:00
GitHub Actions
848148afd5 Auto translate strings 2025-05-02 07:13:21 +00:00
dependabot[bot]
42226adda0 Chore(deps): Bump the frontend-angular-dependencies group (#9848)
Bumps the frontend-angular-dependencies group in /src-ui with 14 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `19.2.10` | `19.2.14` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `19.2.7` | `19.2.9` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `19.2.7` | `19.2.9` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `19.2.7` | `19.2.9` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `19.2.7` | `19.2.9` |
| [@angular/localize](https://github.com/angular/angular) | `19.2.7` | `19.2.9` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `19.2.7` | `19.2.9` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `19.2.7` | `19.2.9` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `19.2.7` | `19.2.9` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `19.2.8` | `19.2.10` |
| [@angular-devkit/core](https://github.com/angular/angular-cli) | `19.2.8` | `19.2.10` |
| [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `19.2.8` | `19.2.10` |
| [@angular/cli](https://github.com/angular/angular-cli) | `19.2.8` | `19.2.10` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `19.2.7` | `19.2.9` |


Updates `@angular/cdk` from 19.2.10 to 19.2.14
- [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.10...19.2.14)

Updates `@angular/common` from 19.2.7 to 19.2.9
- [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.9/packages/common)

Updates `@angular/compiler` from 19.2.7 to 19.2.9
- [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.9/packages/compiler)

Updates `@angular/core` from 19.2.7 to 19.2.9
- [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.9/packages/core)

Updates `@angular/forms` from 19.2.7 to 19.2.9
- [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.9/packages/forms)

Updates `@angular/localize` from 19.2.7 to 19.2.9
- [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.7...19.2.9)

Updates `@angular/platform-browser` from 19.2.7 to 19.2.9
- [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.9/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 19.2.7 to 19.2.9
- [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.9/packages/platform-browser-dynamic)

Updates `@angular/router` from 19.2.7 to 19.2.9
- [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.9/packages/router)

Updates `@angular-devkit/build-angular` from 19.2.8 to 19.2.10
- [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.8...19.2.10)

Updates `@angular-devkit/core` from 19.2.8 to 19.2.10
- [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.8...19.2.10)

Updates `@angular-devkit/schematics` from 19.2.8 to 19.2.10
- [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.8...19.2.10)

Updates `@angular/cli` from 19.2.8 to 19.2.10
- [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.8...19.2.10)

Updates `@angular/compiler-cli` from 19.2.7 to 19.2.9
- [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.9/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-version: 19.2.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-version: 19.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-version: 19.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-version: 19.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-version: 19.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-version: 19.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-version: 19.2.9
  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.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-version: 19.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 19.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/core"
  dependency-version: 19.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/schematics"
  dependency-version: 19.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 19.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 19.2.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-02 07:10:29 +00:00
dependabot[bot]
c2412c0955 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#9849)
Bumps the frontend-eslint-dependencies group in /src-ui with 3 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) and [@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils).


Updates `@typescript-eslint/eslint-plugin` from 8.31.0 to 8.31.1
- [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.31.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.31.0 to 8.31.1
- [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.31.1/packages/parser)

Updates `@typescript-eslint/utils` from 8.31.0 to 8.31.1
- [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.31.1/packages/utils)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.31.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.31.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/utils"
  dependency-version: 8.31.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  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-05-02 06:58:21 +00:00
dependabot[bot]
c58b68e597 Chore(deps-dev): Bump @types/node from 22.13.17 to 22.15.3 in /src-ui (#9850)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.13.17 to 22.15.3.
- [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.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-02 06:47:56 +00:00
shamoon
1b4a52aaa1 Development: always try playwright install on ci, cache browsers (#9847) 2025-05-01 23:34:21 -07:00
shamoon
86adc377a0 Fix: correctly handle empty user for old notes api format, fix frontend API version (#9846) 2025-05-01 14:45:37 -07:00
Trenton H
c83b0bfca6 Fix: Trim off the path portion so the comparision can properly skip (#9839) 2025-05-01 13:50:25 -07:00
shamoon
1b3a91f63f Fix: fix single select in filterable dropdowns when editing (#9834) 2025-04-30 01:48:02 -07:00
shamoon
924a13f724 Fix: always update classifier task result (#9817) 2025-04-29 11:46:18 -07:00
GitHub Actions
7236b8b682 Auto translate strings 2025-04-27 06:32:48 +00:00
shamoon
a3b85c64ca Fixhancement: check more permissions for status consumer messages (#9804) 2025-04-26 23:31:04 -07:00
shamoon
278ef3a364 Documentation: add note that actions can modify the original 2025-04-26 12:20:04 -07:00
dependabot[bot]
0cefdc8475 docker(deps): Bump astral-sh/uv from 0.6.14-python3.12-bookworm-slim to 0.6.16-python3.12-bookworm-slim (#9767)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.6.14-python3.12-bookworm-slim to 0.6.16-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.14...0.6.16)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.6.16-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-04-24 17:14:44 +00:00
dependabot[bot]
915584551c docker-compose(deps): bump gotenberg/gotenberg from 8.19 to 8.20 in /docker/compose (#9661)
Bumps gotenberg/gotenberg from 8.19 to 8.20.

---
updated-dependencies:
- dependency-name: gotenberg/gotenberg
  dependency-version: '8.20'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-24 10:06:07 -07:00
shamoon
855c9aa28d Chore: only use single trigger for pr bot 2025-04-24 07:43:21 -07:00
GitHub Actions
e277a8e1ea Auto translate strings 2025-04-23 16:32:16 +00:00
shamoon
6d8c507169 Chore: auto-generate translation strings (#9462) 2025-04-23 09:30:30 -07:00
shamoon
00050f7c7b Enhancement: support heic images (#9771) 2025-04-23 09:22:21 -07:00
shamoon
5a278381e3 Chore: add coverage for missing lines in patch change 2025-04-22 23:33:11 -07:00
dependabot[bot]
af95963c5f Chore(deps): Bump the frontend-angular-dependencies group (#9768)
Bumps the frontend-angular-dependencies group in /src-ui with 17 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `19.2.7` | `19.2.10` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `19.2.4` | `19.2.7` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `19.2.4` | `19.2.7` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `19.2.4` | `19.2.7` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `19.2.4` | `19.2.7` |
| [@angular/localize](https://github.com/angular/angular) | `19.2.4` | `19.2.7` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `19.2.4` | `19.2.7` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `19.2.4` | `19.2.7` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `19.2.4` | `19.2.7` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `14.2.6` | `14.7.0` |
| [@angular-builders/custom-webpack](https://github.com/just-jeb/angular-builders/tree/HEAD/packages/custom-webpack) | `19.0.0` | `19.0.1` |
| [@angular-builders/jest](https://github.com/just-jeb/angular-builders/tree/HEAD/packages/jest) | `19.0.0` | `19.0.1` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `19.2.5` | `19.2.8` |
| [@angular-devkit/core](https://github.com/angular/angular-cli) | `19.2.5` | `19.2.8` |
| [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `19.2.5` | `19.2.8` |
| [@angular/cli](https://github.com/angular/angular-cli) | `19.2.5` | `19.2.8` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `19.2.4` | `19.2.7` |


Updates `@angular/cdk` from 19.2.7 to 19.2.10
- [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.7...19.2.10)

Updates `@angular/common` from 19.2.4 to 19.2.7
- [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.7/packages/common)

Updates `@angular/compiler` from 19.2.4 to 19.2.7
- [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.7/packages/compiler)

Updates `@angular/core` from 19.2.4 to 19.2.7
- [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.7/packages/core)

Updates `@angular/forms` from 19.2.4 to 19.2.7
- [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.7/packages/forms)

Updates `@angular/localize` from 19.2.4 to 19.2.7
- [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.4...19.2.7)

Updates `@angular/platform-browser` from 19.2.4 to 19.2.7
- [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.7/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 19.2.4 to 19.2.7
- [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.7/packages/platform-browser-dynamic)

Updates `@angular/router` from 19.2.4 to 19.2.7
- [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.7/packages/router)

Updates `@ng-select/ng-select` from 14.2.6 to 14.7.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.2.6...v14.7.0)

Updates `@angular-builders/custom-webpack` from 19.0.0 to 19.0.1
- [Release notes](https://github.com/just-jeb/angular-builders/releases)
- [Changelog](https://github.com/just-jeb/angular-builders/blob/master/packages/custom-webpack/CHANGELOG.md)
- [Commits](https://github.com/just-jeb/angular-builders/commits/@angular-builders/custom-webpack@19.0.1/packages/custom-webpack)

Updates `@angular-builders/jest` from 19.0.0 to 19.0.1
- [Release notes](https://github.com/just-jeb/angular-builders/releases)
- [Changelog](https://github.com/just-jeb/angular-builders/blob/master/packages/jest/CHANGELOG.md)
- [Commits](https://github.com/just-jeb/angular-builders/commits/@angular-builders/jest@19.0.1/packages/jest)

Updates `@angular-devkit/build-angular` from 19.2.5 to 19.2.8
- [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.5...19.2.8)

Updates `@angular-devkit/core` from 19.2.5 to 19.2.8
- [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.5...19.2.8)

Updates `@angular-devkit/schematics` from 19.2.5 to 19.2.8
- [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.5...19.2.8)

Updates `@angular/cli` from 19.2.5 to 19.2.8
- [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.5...19.2.8)

Updates `@angular/compiler-cli` from 19.2.4 to 19.2.7
- [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.7/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-version: 19.2.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-version: 19.2.7
  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.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@ng-select/ng-select"
  dependency-version: 14.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-builders/custom-webpack"
  dependency-version: 19.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-builders/jest"
  dependency-version: 19.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 19.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/core"
  dependency-version: 19.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/schematics"
  dependency-version: 19.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 19.2.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 19.2.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-22 16:34:21 -07:00
dependabot[bot]
c724436fc4 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#9770)
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.29.0 to 8.31.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.31.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.29.0 to 8.31.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.31.0/packages/parser)

Updates `@typescript-eslint/utils` from 8.29.0 to 8.31.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.31.0/packages/utils)

Updates `eslint` from 9.23.0 to 9.25.1
- [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.23.0...v9.25.1)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/utils"
  dependency-version: 8.31.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  dependency-version: 9.25.1
  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-04-22 22:46:35 +00:00
dependabot[bot]
e288824963 Chore(deps-dev): Bump jest-preset-angular (#9769)
Bumps the frontend-jest-dependencies group in /src-ui with 1 update: [jest-preset-angular](https://github.com/thymikee/jest-preset-angular).


Updates `jest-preset-angular` from 14.5.4 to 14.5.5
- [Release notes](https://github.com/thymikee/jest-preset-angular/releases)
- [Changelog](https://github.com/thymikee/jest-preset-angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/thymikee/jest-preset-angular/compare/v14.5.4...v14.5.5)

---
updated-dependencies:
- dependency-name: jest-preset-angular
  dependency-version: 14.5.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-jest-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-22 15:36:36 -07:00
shamoon
312bb743b9 Chore: add ymlfmt (#9745) 2025-04-22 22:20:54 +00:00
shamoon
7436a97684 Enhancement: support allauth disable unknown account emails (#9743) 2025-04-22 21:58:33 +00:00
shamoon
cbaceb95af Enhancement: use patch instead of put for frontend document changes (#9744) 2025-04-22 19:58:28 +00:00
dependabot[bot]
2abf38f98e Chore(deps): Bump the small-changes group across 1 directory with 6 updates (#9764)
* Chore(deps): Bump the small-changes group across 1 directory with 6 updates

Bumps the small-changes group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [channels](https://github.com/django/channels) | `4.2.0` | `4.2.2` |
| [filelock](https://github.com/tox-dev/py-filelock) | `3.17.0` | `3.18.0` |
| [gotenberg-client](https://github.com/stumpylog/gotenberg-client) | `0.9.0` | `0.10.0` |
| [jinja2](https://github.com/pallets/jinja) | `3.1.5` | `3.1.6` |
| [python-dotenv](https://github.com/theskumar/python-dotenv) | `1.0.1` | `1.1.0` |
| [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) | `3.12.1` | `3.13.0` |



Updates `channels` from 4.2.0 to 4.2.2
- [Changelog](https://github.com/django/channels/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/channels/compare/4.2.0...4.2.2)

Updates `filelock` from 3.17.0 to 3.18.0
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.17.0...3.18.0)

Updates `gotenberg-client` from 0.9.0 to 0.10.0
- [Release notes](https://github.com/stumpylog/gotenberg-client/releases)
- [Changelog](https://github.com/stumpylog/gotenberg-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/gotenberg-client/compare/0.9.0...0.10.0)

Updates `jinja2` from 3.1.5 to 3.1.6
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6)

Updates `python-dotenv` from 1.0.1 to 1.1.0
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.0.1...v1.1.0)

Updates `rapidfuzz` from 3.12.1 to 3.13.0
- [Release notes](https://github.com/rapidfuzz/RapidFuzz/releases)
- [Changelog](https://github.com/rapidfuzz/RapidFuzz/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/rapidfuzz/RapidFuzz/compare/v3.12.1...v3.13.0)

---
updated-dependencies:
- dependency-name: channels
  dependency-version: 4.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: filelock
  dependency-version: 3.18.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: gotenberg-client
  dependency-version: 0.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: jinja2
  dependency-version: 3.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: python-dotenv
  dependency-version: 1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: rapidfuzz
  dependency-version: 3.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

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

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-04-22 10:32:27 -07:00
dependabot[bot]
b40e1f7d01 Chore(deps): Bump the django group across 1 directory with 6 updates (#9753)
Bumps the django group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [django](https://github.com/django/django) | `5.1.7` | `5.1.8` |
| [django-celery-results](https://github.com/celery/django-celery-results) | `2.5.1` | `2.6.0` |
| [django-extensions](https://github.com/django-extensions/django-extensions) | `3.2.3` | `4.1` |
| [djangorestframework](https://github.com/encode/django-rest-framework) | `3.15.2` | `3.16.0` |
| [drf-spectacular-sidecar](https://github.com/tfranzel/drf-spectacular-sidecar) | `2025.3.1` | `2025.4.1` |
| [drf-writable-nested](https://github.com/beda-software/drf-writable-nested) | `0.7.1` | `0.7.2` |



Updates `django` from 5.1.7 to 5.1.8
- [Commits](https://github.com/django/django/compare/5.1.7...5.1.8)

Updates `django-celery-results` from 2.5.1 to 2.6.0
- [Release notes](https://github.com/celery/django-celery-results/releases)
- [Changelog](https://github.com/celery/django-celery-results/blob/main/Changelog)
- [Commits](https://github.com/celery/django-celery-results/compare/v2.5.1...v2.6.0)

Updates `django-extensions` from 3.2.3 to 4.1
- [Release notes](https://github.com/django-extensions/django-extensions/releases)
- [Changelog](https://github.com/django-extensions/django-extensions/blob/main/CHANGELOG.md)
- [Commits](https://github.com/django-extensions/django-extensions/compare/3.2.3...4.1)

Updates `djangorestframework` from 3.15.2 to 3.16.0
- [Release notes](https://github.com/encode/django-rest-framework/releases)
- [Commits](https://github.com/encode/django-rest-framework/compare/3.15.2...3.16.0)

Updates `drf-spectacular-sidecar` from 2025.3.1 to 2025.4.1
- [Commits](https://github.com/tfranzel/drf-spectacular-sidecar/compare/2025.3.1...2025.4.1)

Updates `drf-writable-nested` from 0.7.1 to 0.7.2
- [Release notes](https://github.com/beda-software/drf-writable-nested/releases)
- [Changelog](https://github.com/beda-software/drf-writable-nested/blob/master/CHANGELOG.md)
- [Commits](https://github.com/beda-software/drf-writable-nested/compare/v0.7.1...v0.7.2)

---
updated-dependencies:
- dependency-name: django
  dependency-version: 5.1.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: django
- dependency-name: django-celery-results
  dependency-version: 2.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: django
- dependency-name: django-extensions
  dependency-version: '4.1'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: django
- dependency-name: djangorestframework
  dependency-version: 3.16.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: django
- dependency-name: drf-spectacular-sidecar
  dependency-version: 2025.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: django
- dependency-name: drf-writable-nested
  dependency-version: 0.7.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: django
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-22 09:18:22 -07:00
shamoon
15d4ac8ba2 Fixhancement: tag plus button should add tag to doc (#9762) 2025-04-22 08:02:55 -07:00
shamoon
fcb7349e8e Fix: fix zoom increase/decrease buttons in FF (#9761) 2025-04-22 08:02:43 -07:00
Sebastian Steinbeißer
648cfd9d05 Chore: switch from os.path to pathlib.Path (#9339) 2025-04-21 12:16:52 -07:00
Trenton H
c3df7d3439 Chore: Group additional Django dependencies together (#9741) 2025-04-21 12:16:34 -07:00
shamoon
a5cd545a1b Chore: replace secretary with GHA (#9723) 2025-04-21 19:02:54 +00:00
dependabot[bot]
96227f785a docker(deps): bump astral-sh/uv from 0.6.13-python3.12-bookworm-slim to 0.6.14-python3.12-bookworm-slim (#9656)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.6.13-python3.12-bookworm-slim to 0.6.14-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.13...0.6.14)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.6.14-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-04-21 18:46:19 +00:00
shamoon
7f98c4b794 Fix: include subpath in drf-spectacular settings if set (#9738) 2025-04-21 11:24:08 -07:00
github-actions[bot]
d914a82dea Changelog v2.15.3 - GHA (#9720)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-19 16:15:35 -07:00
shamoon
1a72efa3c9 Bump version to 2.15.3 2025-04-19 15:34:42 -07:00
shamoon
aa6c58103f Merge branch 'dev' 2025-04-19 15:34:04 -07:00
github-actions[bot]
ba6976bdbb New Crowdin translations by GitHub Action (#9705) 2025-04-19 15:15:46 -07:00
shamoon
7a1be6bff2 Update translation strings 2025-04-19 15:13:53 -07:00
shamoon
c3bd587cbf Documentation: note api v8 bump 2025-04-19 15:12:55 -07:00
shamoon
a57a3dbb30 Fix: do not try deleting original that was moved to trash dir (#9684) 2025-04-19 15:11:29 -07:00
shamoon
f52ebc7bf0 Fix: preserve non-ASCII filenames in document downloads (#9702) 2025-04-19 22:10:34 +00:00
shamoon
abf910fd93 Fix: fix breaking api change to document notes user field (#9714) 2025-04-19 22:02:33 +00:00
shamoon
1b0aa193bd Fix: fix missing archive file size 2025-04-19 14:26:53 -07:00
shamoon
eac12fe031 Documentation: tweaks to sharing, links 2025-04-18 09:49:34 -07:00
shamoon
62f46b706e Fix: another doc link fix (#9700) 2025-04-17 21:15:53 -07:00
shamoon
df5af5c8ad Fix: correctly handle dict data with webhook (#9674) 2025-04-16 00:07:08 -07:00
shamoon
67a97ffc4d Documentation: expound email config explanation 2025-04-15 07:52:53 -07:00
github-actions[bot]
45b39f36d6 Documentation: Add v2.15.2 changelog (#9660) 2025-04-14 14:08:18 -07:00
shamoon
a339853bc5 Merge branch 'dev' 2025-04-14 13:25:39 -07:00
shamoon
6c2e06d40b Bump version to 2.15.2 2025-04-14 13:04:21 -07:00
github-actions[bot]
2d6a4d108f New Crowdin translations by GitHub Action (#9655)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-14 12:49:25 -07:00
github-actions[bot]
703db0f01f New Crowdin translations by GitHub Action (#9611)
* New Crowdin translations by GitHub Action

* Trigger ci

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-04-14 12:47:38 -07:00
Trenton H
8b5d6bf3db Chore: Upgrades granian to latest patch version (#9652) 2025-04-14 16:03:25 +00:00
shamoon
2d93e95848 Update translation strings 2025-04-14 08:54:58 -07:00
Trenton H
ab8c75958d Fix: Adds better handling during folder checking/creation/permissions for non-root (#9616)
* Adds better handling during folder checking/creation/permissions for when the image is running as non-root

* Prefers the long options to commands
2025-04-14 15:51:57 +00:00
shamoon
9db3923d35 Tweak: consistently use created date when displaying doc in list (#9651) 2025-04-14 08:27:30 -07:00
Trenton H
e2860ed36d Fix: Explicitly set the HOME environment variable for running as root at startup (#9643)
* Explicitly set the HOME environment for the migrations to fix issue with certificates

* Defines the HOME globally when we're running as root for startup
2025-04-14 15:21:45 +00:00
Thom Wiggers
82a5680217 Delete unused docker/docker-entrypoint.sh (#9615) 2025-04-14 07:39:56 -07:00
Hannes Ortmeier
588fd0207d chore: Bump celery to 5.5.1 (#9642) 2025-04-14 14:28:04 +00:00
shamoon
6dea158de9 Fix: prevent self-linking when bulk edit doc link (#9629) 2025-04-14 07:12:50 -07:00
shamoon
d956269d5f Update faq 2025-04-10 20:25:16 -07:00
github-actions[bot]
f269919410 Documentation: Add v2.15.1 changelog (#9606)
---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-04-09 15:48:56 -07:00
shamoon
5f00066dff Manually bump version in uv.lock 2025-04-09 15:48:20 -07:00
Trenton Holmes
705f542129 Bumps the version to 2.15.1 2025-04-09 14:55:02 -07:00
Trenton Holmes
f036292b72 Merge remote-tracking branch 'origin/dev' 2025-04-09 14:53:31 -07:00
Crowdin Bot
f8a43d5dab New Crowdin translations by GitHub Action 2025-04-09 21:44:21 +00:00
dependabot[bot]
53c106d448 docker(deps): Bump astral-sh/uv (#9573)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.6.9-python3.12-bookworm-slim to 0.6.13-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.9...0.6.13)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.6.13-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-04-09 21:43:12 +00:00
shamoon
0f37186c88 Chore: use whoosh-reloaded (#9605) 2025-04-09 21:31:29 +00:00
Trenton H
0fb55f3ae8 Fix: Run migration lock as the correct user (#9604) 2025-04-09 21:15:38 +00:00
Trenton H
78822f6121 Fix: Adds a warning to the user if their secret file includes a trailing newline (#9601) 2025-04-09 21:05:26 +00:00
shamoon
2ee1d7540e Chore: add coverage for frontend download content-disposition parsing 2025-04-09 13:23:40 -07:00
shamoon
43b2527275 Fix: correct download filename in 2.15.0 (#9599)
---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2025-04-09 16:03:38 +00:00
Nathanaël Houn
248b573c03 Documentation: update crowdin links (#9595) 2025-04-09 08:01:21 -07:00
shamoon
b9f7428f2f Fix: include matching check for scheduled workflows (#9594) 2025-04-09 02:12:42 -07:00
github-actions[bot]
e35dad81d9 Documentation: Add v2.15.0 changelog (#9581)
* Changelog v2.15.0 - GHA

* Re-categorize a PR

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-04-08 14:10:48 -07:00
shamoon
7d40eb2e24 Merge pull request #9402 from paperless-ngx/beta
[Beta] Paperless-ngx v2.15.0 Beta Release
2025-04-08 09:45:19 -07:00
shamoon
d2783a3fbe Merge branch 'dev' into beta 2025-04-08 09:03:26 -07:00
github-actions[bot]
8ad794e189 New Crowdin translations by GitHub Action (#9479)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-04-08 09:02:42 -07:00
shamoon
358db10fe3 Fix: ensure only matched scheduled workflows are applied (#9580) 2025-04-08 08:55:03 -07:00
shamoon
556f47fe12 Merge branch 'dev' into beta 2025-04-04 08:43:12 -07:00
shamoon
0d5a2b4382 Fix: fix large doc thumb hidden at unexpected screen sizes (#9559) 2025-04-04 08:31:34 -07:00
dependabot[bot]
c9bc9acd1a docker-compose(deps): Bump gotenberg/gotenberg in /docker/compose (#9532)
Bumps gotenberg/gotenberg from 8.17 to 8.19.

---
updated-dependencies:
- dependency-name: gotenberg/gotenberg
  dependency-version: '8.19'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-02 18:53:44 +00:00
dependabot[bot]
e9e209d290 docker(deps): Bump astral-sh/uv (#9531)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.6.9-python3.12-bookworm-slim to 0.6.11-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.9...0.6.11)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  dependency-version: 0.6.11-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-04-02 18:43:58 +00:00
shamoon
2e593a0022 Fix: fix potential race condition when creating new custom fields on doc details (#9542) 2025-04-02 10:00:25 -07:00
dependabot[bot]
3526a4cf23 Chore(deps): Bump the frontend-angular-dependencies group (#9536)
Bumps the frontend-angular-dependencies group in /src-ui with 20 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `19.2.2` | `19.2.7` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `19.2.1` | `19.2.4` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `19.2.1` | `19.2.4` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `19.2.1` | `19.2.4` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `19.2.1` | `19.2.4` |
| [@angular/localize](https://github.com/angular/angular) | `19.2.1` | `19.2.4` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `19.2.1` | `19.2.4` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `19.2.1` | `19.2.4` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `19.2.1` | `19.2.4` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `14.2.3` | `14.2.6` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `19.2.1` | `19.2.5` |
| [@angular-devkit/core](https://github.com/angular/angular-cli) | `19.2.1` | `19.2.5` |
| [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `19.2.1` | `19.2.5` |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `19.2.1` | `19.3.0` |
| [@angular-eslint/eslint-plugin](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin) | `19.2.1` | `19.3.0` |
| [@angular-eslint/eslint-plugin-template](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template) | `19.2.1` | `19.3.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `19.2.1` | `19.3.0` |
| [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) | `19.2.1` | `19.3.0` |
| [@angular/cli](https://github.com/angular/angular-cli) | `19.2.1` | `19.2.5` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `19.2.1` | `19.2.4` |


Updates `@angular/cdk` from 19.2.2 to 19.2.7
- [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.2...19.2.7)

Updates `@angular/common` from 19.2.1 to 19.2.4
- [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.4/packages/common)

Updates `@angular/compiler` from 19.2.1 to 19.2.4
- [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.4/packages/compiler)

Updates `@angular/core` from 19.2.1 to 19.2.4
- [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.4/packages/core)

Updates `@angular/forms` from 19.2.1 to 19.2.4
- [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.4/packages/forms)

Updates `@angular/localize` from 19.2.1 to 19.2.4
- [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.1...19.2.4)

Updates `@angular/platform-browser` from 19.2.1 to 19.2.4
- [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.4/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 19.2.1 to 19.2.4
- [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.4/packages/platform-browser-dynamic)

Updates `@angular/router` from 19.2.1 to 19.2.4
- [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.4/packages/router)

Updates `@ng-select/ng-select` from 14.2.3 to 14.2.6
- [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.2.3...v14.2.6)

Updates `@angular-devkit/build-angular` from 19.2.1 to 19.2.5
- [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.1...19.2.5)

Updates `@angular-devkit/core` from 19.2.1 to 19.2.5
- [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.1...19.2.5)

Updates `@angular-devkit/schematics` from 19.2.1 to 19.2.5
- [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.1...19.2.5)

Updates `@angular-eslint/builder` from 19.2.1 to 19.3.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.3.0/packages/builder)

Updates `@angular-eslint/eslint-plugin` from 19.2.1 to 19.3.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.3.0/packages/eslint-plugin)

Updates `@angular-eslint/eslint-plugin-template` from 19.2.1 to 19.3.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.3.0/packages/eslint-plugin-template)

Updates `@angular-eslint/schematics` from 19.2.1 to 19.3.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.3.0/packages/schematics)

Updates `@angular-eslint/template-parser` from 19.2.1 to 19.3.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.3.0/packages/template-parser)

Updates `@angular/cli` from 19.2.1 to 19.2.5
- [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.1...19.2.5)

Updates `@angular/compiler-cli` from 19.2.1 to 19.2.4
- [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.4/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-version: 19.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-version: 19.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-version: 19.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-version: 19.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-version: 19.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-version: 19.2.4
  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.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-version: 19.2.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@ng-select/ng-select"
  dependency-version: 14.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/core"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/schematics"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/builder"
  dependency-version: 19.3.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.3.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.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/schematics"
  dependency-version: 19.3.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.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-version: 19.2.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-version: 19.2.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 20:29:20 -07:00
dependabot[bot]
f4791cac2d Chore(deps-dev): Bump the frontend-eslint-dependencies group (#9538)
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.26.1 to 8.29.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.29.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.26.1 to 8.29.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.29.0/packages/parser)

Updates `@typescript-eslint/utils` from 8.26.1 to 8.29.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.29.0/packages/utils)

Updates `eslint` from 9.22.0 to 9.23.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.22.0...v9.23.0)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/utils"
  dependency-version: 8.29.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  dependency-version: 9.23.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-04-01 21:38:33 +00:00
dependabot[bot]
fdafd4eefb Chore(deps-dev): Bump @types/node from 22.13.9 to 22.13.17 in /src-ui (#9539)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.13.9 to 22.13.17.
- [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.13.17
  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-04-01 21:27:52 +00:00
dependabot[bot]
87a8847a8d Chore(deps-dev): Bump jest-preset-angular (#9537)
Bumps the frontend-jest-dependencies group in /src-ui with 1 update: [jest-preset-angular](https://github.com/thymikee/jest-preset-angular).


Updates `jest-preset-angular` from 14.5.3 to 14.5.4
- [Release notes](https://github.com/thymikee/jest-preset-angular/releases)
- [Changelog](https://github.com/thymikee/jest-preset-angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/thymikee/jest-preset-angular/compare/v14.5.3...v14.5.4)

---
updated-dependencies:
- dependency-name: jest-preset-angular
  dependency-version: 14.5.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-jest-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 21:17:31 +00:00
dependabot[bot]
7c31c79bbc Chore(deps-dev): Bump @playwright/test from 1.50.1 to 1.51.1 in /src-ui (#9540)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.50.1 to 1.51.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.50.1...v1.51.1)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-version: 1.51.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 14:07:29 -07:00
shamoon
bf2a9b02c6 Fix: correct tag removal button type, prevent dropdown open 2025-04-01 08:05:46 -07:00
shamoon
348858780c Merge branch 'beta' into dev 2025-03-31 19:35:30 -07:00
shamoon
eb481ac1c0 Fix: fix doc link input (#9533) 2025-03-31 19:35:05 -07:00
shamoon
9a2d7a64ac Merge branch 'beta' into dev 2025-03-29 10:13:41 -07:00
shamoon
32a7f9cd5a Enhancement: allow webUI first account signup (#9500) 2025-03-29 17:12:34 +00:00
shamoon
92431b2f4b Merge branch 'beta' into dev 2025-03-29 10:06:01 -07:00
shamoon
b4b2a92225 Fix: fix unshared items display 2025-03-28 05:47:10 -07:00
shamoon
fd45e81a83 Fix: fix cf dropdown placement on mobile (#9508) 2025-03-27 14:09:51 -07:00
Timon
97d59dce9c Documentation: correct static url description (#9506) 2025-03-27 13:24:41 -07:00
dependabot[bot]
f3479d982c Chore(deps): Bump django from 5.1.6 to 5.1.7 in the django group (#9486)
* Chore(deps): Bump django from 5.1.6 to 5.1.7 in the django group

Bumps the django group with 1 update: [django](https://github.com/django/django).

Updates `django` from 5.1.6 to 5.1.7
- [Commits](https://github.com/django/django/compare/5.1.6...5.1.7)

---
updated-dependencies:
- dependency-name: django
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: django
...

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

* Fixes the missing extras

---------

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-03-26 21:44:49 +00:00
dependabot[bot]
b3ba673d9a docker(deps): Bump astral-sh/uv from 0.6.5-python3.12-bookworm-slim to 0.6.9-python3.12-bookworm-slim (#9488)
Bumps [astral-sh/uv](https://github.com/astral-sh/uv) from 0.6.5-python3.12-bookworm-slim to 0.6.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.6.5...0.6.9)

---
updated-dependencies:
- dependency-name: astral-sh/uv
  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-03-26 21:31:45 +00:00
Trenton H
68b7427640 Merge remote-tracking branch 'origin/dev' into beta 2025-03-26 14:19:31 -07:00
Trenton H
9c68100dc0 Fix: Make management commands aware of the container environment (#9499) 2025-03-26 14:17:10 -07:00
Trenton H
6e694ad9ff Switch to using uvloop and upgrade granian for some ASGI fixes (#9494) 2025-03-25 21:41:29 -07:00
shamoon
5db511afdf Fix: revert removed x-frame-options header in non-debug 2025-03-24 07:28:27 -07:00
shamoon
a8de26f88a Fix: only overwrite existing cf values in workflow if set (#9459) 2025-03-23 17:25:15 -07:00
shamoon
7a07f1e81d Remove unnecessary check 2025-03-21 10:28:20 -07:00
shamoon
92524ae97a Chore: remove a couple of console logs 2025-03-21 10:24:59 -07:00
shamoon
1c89f6da24 More narrow device tweaks 2025-03-21 10:24:59 -07:00
shamoon
d1a3e3b859 Fix: top nav layout with custom title on very narrow screens 2025-03-21 10:24:58 -07:00
shamoon
79ae594d54 Fix: fix saving docs with notes 2025-03-21 10:24:58 -07:00
shamoon
f753f6dc46 Merge branch 'dev' into beta 2025-03-21 09:52:50 -07:00
github-actions[bot]
97fe5c4176 New Crowdin translations by GitHub Action (#9438)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-03-21 09:52:32 -07:00
shamoon
1f5086164b Chore: remove a couple of console logs 2025-03-21 09:52:06 -07:00
shamoon
e60bd3a132 Fix: fix auto-close when doc update no longer has permissions (#9453) 2025-03-21 09:50:04 -07:00
shamoon
b4047e73bb More narrow device tweaks 2025-03-21 00:52:28 -07:00
shamoon
4263d2196c Fix: top nav layout with custom title on very narrow screens 2025-03-21 00:44:37 -07:00
shamoon
ac780134fb Merge branch 'dev' into beta 2025-03-18 19:18:39 -07:00
github-actions[bot]
5d6cfa7349 New Crowdin translations by GitHub Action (#9430)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-03-18 19:17:49 -07:00
shamoon
3105317137 Fix: close email dialog after email send 2025-03-18 19:17:49 -07:00
shamoon
1d9482acc3 Merge branch 'dev' into beta 2025-03-17 23:58:23 -07:00
github-actions[bot]
1456169d7f New Crowdin translations by GitHub Action (#9408)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-03-17 23:36:52 -07:00
shamoon
22a6fe5e10 Enhancement: support more 'not assigned' filtering, refactor (#9429) 2025-03-17 23:35:03 -07:00
shamoon
caa3c13edd Fix: fix saving docs with notes 2025-03-16 08:31:27 -07:00
shamoon
24e863b298 Coverage 2025-03-14 13:23:42 -07:00
shamoon
0c9d615f56 Merge branch 'main' into beta 2025-03-14 13:07:03 -07:00
shamoon
90561857e8 Documentation: add note about WAL mode with SQLite 2025-02-24 14:08:49 -08:00
shamoon
fc68f55d1a Documentation: correct modify_tags param requirement 2025-02-14 08:20:43 -08:00
shamoon
6a8ec182fa Documentation: clarify encryption docs 2025-02-08 08:07:04 -08:00
Stéphane Brunner
69541546ea Fix URL to django-rest-framework (#8998) 2025-02-02 08:00:02 -08:00
289 changed files with 64096 additions and 39365 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

@@ -21,19 +21,17 @@
# This file is intended only to be used through VSCOde devcontainers. See README.md # This file is intended only to be used through VSCOde devcontainers. See README.md
# in the folder .devcontainer. # in the folder .devcontainer.
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:7
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- ./redisdata:/data - ./redisdata:/data
# No ports need to be exposed; the VSCode DevContainer plugin manages them. # No ports need to be exposed; the VSCode DevContainer plugin manages them.
paperless-development: paperless-development:
image: paperless-ngx image: paperless-ngx
build: build:
context: ../ # Dockerfile cannot access files from parent directories if context is not set. context: ../ # Dockerfile cannot access files from parent directories if context is not set.
dockerfile: ./.devcontainer/Dockerfile dockerfile: ./.devcontainer/Dockerfile
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
@@ -60,25 +58,20 @@ services:
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
PAPERLESS_STATICDIR: ./src/documents/static PAPERLESS_STATICDIR: ./src/documents/static
PAPERLESS_DEBUG: true PAPERLESS_DEBUG: true
# Overrides default command so things don't shut down after the process ends. # Overrides default command so things don't shut down after the process ends.
command: /bin/sh -c "chown -R paperless:paperless /usr/src/paperless/paperless-ngx/src/documents/static/frontend && chown -R paperless:paperless /usr/src/paperless/paperless-ngx/.ruff_cache && while sleep 1000; do :; done" command: /bin/sh -c "chown -R paperless:paperless /usr/src/paperless/paperless-ngx/src/documents/static/frontend && chown -R paperless:paperless /usr/src/paperless/paperless-ngx/.ruff_cache && while sleep 1000; do :; done"
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.17 image: docker.io/gotenberg/gotenberg:8.17
restart: unless-stopped restart: unless-stopped
# The Gotenberg Chromium route is used to convert .eml files. We do not # The Gotenberg Chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even JavaScript. # want to allow external content like tracking pixels or even JavaScript.
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:latest image: docker.io/apache/tika:latest
restart: unless-stopped restart: unless-stopped
volumes: volumes:
data: data:
media: media:

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

@@ -5,7 +5,6 @@ version: 2
# Required for uv support for now # Required for uv support for now
enable-beta-ecosystems: true enable-beta-ecosystems: true
updates: updates:
# Enable version updates for pnpm # Enable version updates for pnpm
- package-ecosystem: "npm" - package-ecosystem: "npm"
target-branch: "dev" target-branch: "dev"
@@ -17,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:
@@ -35,7 +31,6 @@ updates:
patterns: patterns:
- "@typescript-eslint*" - "@typescript-eslint*"
- "eslint" - "eslint"
# Enable version updates for Python # Enable version updates for Python
- package-ecosystem: "uv" - package-ecosystem: "uv"
target-branch: "dev" target-branch: "dev"
@@ -46,9 +41,6 @@ updates:
labels: labels:
- "backend" - "backend"
- "dependencies" - "dependencies"
# Add reviewers
reviewers:
- "paperless-ngx/backend"
groups: groups:
development: development:
patterns: patterns:
@@ -59,6 +51,7 @@ updates:
django: django:
patterns: patterns:
- "*django*" - "*django*"
- "drf-*"
major-versions: major-versions:
update-types: update-types:
- "major" - "major"
@@ -66,11 +59,13 @@ updates:
update-types: update-types:
- "minor" - "minor"
- "patch" - "patch"
exclude-patterns:
- "*django*"
- "drf-*"
pre-built: pre-built:
patterns: patterns:
- psycopg* - psycopg*
- zxing-cpp - zxing-cpp
# Enable updates for GitHub Actions # Enable updates for GitHub Actions
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
target-branch: "dev" target-branch: "dev"
@@ -81,41 +76,32 @@ updates:
labels: labels:
- "ci-cd" - "ci-cd"
- "dependencies" - "dependencies"
# Add reviewers
reviewers:
- "paperless-ngx/ci-cd"
groups: groups:
actions: actions:
update-types: update-types:
- "major" - "major"
- "minor" - "minor"
- "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:
- "ci-cd"
- "dependencies" - "dependencies"
commit-message: commit-message:
prefix: "docker" prefix: "docker"
include: "scope" include: "scope"
# Update Docker Compose files in docker/compose directory # Update Docker Compose files in docker/compose directory
- package-ecosystem: "docker-compose" - package-ecosystem: "docker-compose"
directory: "/docker/compose/" directory: "/docker/compose/"
schedule: schedule:
interval: "weekly" interval: "weekly"
open-pull-requests-limit: 5 open-pull-requests-limit: 5
reviewers:
- "paperless-ngx/ci-cd"
labels: labels:
- "ci-cd"
- "dependencies" - "dependencies"
commit-message: commit-message:
prefix: "docker-compose" prefix: "docker-compose"

26
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
backend:
- changed-files:
- any-glob-to-any-file:
- 'src/**'
- 'pyproject.toml'
- 'uv.lock'
- 'requirements.txt'
frontend:
- changed-files:
- any-glob-to-any-file:
- 'src-ui/**'
documentation:
- changed-files:
- any-glob-to-any-file:
- 'docs/**'
ci-cd:
- changed-files:
- any-glob-to-any-file:
- '.github/**'
# pr types
bug:
- head-branch:
- ['^fix']
enhancement:
- head-branch:
- ['^feature']

View File

@@ -1,15 +1,3 @@
autolabeler:
- label: "bug"
branch:
- '/^fix/'
title:
- "/^fix/i"
- "/^Bugfix/i"
- label: "enhancement"
branch:
- '/^feature/'
title:
- "/^feature/i"
categories: categories:
- title: 'Breaking Changes' - title: 'Breaking Changes'
labels: labels:
@@ -17,7 +5,7 @@ categories:
- title: 'Notable Changes' - title: 'Notable Changes'
labels: labels:
- 'notable' - 'notable'
- title: 'Features' - title: 'Features / Enhancements'
labels: labels:
- 'enhancement' - 'enhancement'
- title: 'Bug Fixes' - title: 'Bug Fixes'

View File

@@ -1,5 +1,4 @@
name: ci name: ci
on: on:
push: push:
tags: tags:
@@ -12,72 +11,57 @@ on:
pull_request: pull_request:
branches-ignore: branches-ignore:
- 'translations**' - '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:
pre-commit: pre-commit:
# We want to run on external PRs, but not on our own internal PRs as they'll be run # We want to run on external PRs, but not on our own internal PRs as they'll be run
# by the push to the branch. Without this if check, checks are duplicated since # by the push to the branch. Without this if check, checks are duplicated since
# internal PRs match both the push and pull_request events. # internal PRs match both the push and pull_request events.
if: if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
github.event_name == 'push' || github.event.pull_request.head.repo.full_name !=
github.repository
name: Linting Checks name: Linting Checks
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- - name: Checkout repository
name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- - name: Install python
name: Install python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- - name: Check files
name: Check files
uses: pre-commit/action@v3.0.1 uses: pre-commit/action@v3.0.1
documentation: documentation:
name: "Build & Deploy Documentation" name: "Build & Deploy Documentation"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- pre-commit - pre-commit
steps: steps:
- - name: Checkout
name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- - name: Set up Python
name: Set up Python
id: setup-python id: setup-python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- - name: Install uv
name: Install uv uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v5
with: with:
version: ${{ env.DEFAULT_UV_VERSION }} version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true enable-cache: true
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- - name: Install Python dependencies
name: Install Python dependencies
run: | run: |
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
- - name: Make documentation
name: Make documentation
run: | run: |
uv run \ uv run \
--python ${{ steps.setup-python.outputs.python-version }} \ --python ${{ steps.setup-python.outputs.python-version }} \
--dev \ --dev \
--frozen \ --frozen \
mkdocs build --config-file ./mkdocs.yml mkdocs build --config-file ./mkdocs.yml
- - name: Deploy documentation
name: Deploy documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/main' if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: | run: |
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME" echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
@@ -88,14 +72,12 @@ jobs:
--dev \ --dev \
--frozen \ --frozen \
mkdocs gh-deploy --force --no-history mkdocs gh-deploy --force --no-history
- - name: Upload artifact
name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: documentation name: documentation
path: site/ path: site/
retention-days: 7 retention-days: 7
tests-backend: tests-backend:
name: "Backend Tests (Python ${{ matrix.python-version }})" name: "Backend Tests (Python ${{ matrix.python-version }})"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -106,49 +88,40 @@ jobs:
python-version: ['3.10', '3.11', '3.12'] python-version: ['3.10', '3.11', '3.12']
fail-fast: false fail-fast: false
steps: steps:
- - name: Checkout
name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- - name: Start containers
name: Start containers
run: | run: |
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml pull --quiet docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml pull --quiet
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml up --detach docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml up --detach
- - name: Set up Python
name: Set up Python
id: setup-python id: setup-python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: "${{ matrix.python-version }}" python-version: "${{ matrix.python-version }}"
- - name: Install uv
name: Install uv uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v5
with: with:
version: ${{ env.DEFAULT_UV_VERSION }} version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true enable-cache: true
python-version: ${{ steps.setup-python.outputs.python-version }} python-version: ${{ steps.setup-python.outputs.python-version }}
- - name: Install system dependencies
name: Install system dependencies
run: | run: |
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript libzbar0 poppler-utils sudo apt-get install -qq --no-install-recommends unpaper tesseract-ocr imagemagick ghostscript libzbar0 poppler-utils
- - name: Configure ImageMagick
name: Configure ImageMagick
run: | run: |
sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml sudo cp docker/rootfs/etc/ImageMagick-6/paperless-policy.xml /etc/ImageMagick-6/policy.xml
- - name: Install Python dependencies
name: Install Python dependencies
run: | run: |
uv sync \ uv sync \
--python ${{ steps.setup-python.outputs.python-version }} \ --python ${{ steps.setup-python.outputs.python-version }} \
--group testing \ --group testing \
--frozen --frozen
- - name: List installed Python dependencies
name: List installed Python dependencies
run: | run: |
uv pip list uv pip list
- - name: Tests
name: Tests
env: env:
PAPERLESS_CI_TEST: 1 PAPERLESS_CI_TEST: 1
# Enable paperless_mail testing against real server # Enable paperless_mail testing against real server
@@ -161,28 +134,24 @@ jobs:
--dev \ --dev \
--frozen \ --frozen \
pytest pytest
- - name: Upload backend test results to Codecov
name: Upload backend test results to Codecov
if: always() if: always()
uses: codecov/test-results-action@v1 uses: codecov/test-results-action@v1
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: backend-python-${{ matrix.python-version }} flags: backend-python-${{ matrix.python-version }}
files: junit.xml files: junit.xml
- - name: Upload backend coverage to Codecov
name: Upload backend coverage to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: backend-python-${{ matrix.python-version }} flags: backend-python-${{ matrix.python-version }}
files: coverage.xml files: coverage.xml
- - name: Stop containers
name: Stop containers
if: always() if: always()
run: | run: |
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml logs docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml logs
docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml down docker compose --file ${{ github.workspace }}/docker/compose/docker-compose.ci-test.yml down
install-frontend-dependencies: install-frontend-dependencies:
name: "Install Frontend Dependencies" name: "Install Frontend Dependencies"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -194,8 +163,7 @@ jobs:
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 10 version: 10
- - name: Use Node.js 20
name: Use Node.js 20
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: 20.x
@@ -209,17 +177,10 @@ jobs:
~/.pnpm-store ~/.pnpm-store
~/.cache ~/.cache
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }} key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
- - name: Install dependencies
name: Install dependencies
if: steps.cache-frontend-deps.outputs.cache-hit != 'true'
run: cd src-ui && pnpm install run: cd src-ui && pnpm install
-
name: Install Playwright
if: steps.cache-frontend-deps.outputs.cache-hit != 'true'
run: cd src-ui && pnpm playwright install --with-deps
tests-frontend: tests-frontend:
name: "Frontend Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})" name: "Frontend Unit Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- install-frontend-dependencies - install-frontend-dependencies
@@ -235,8 +196,7 @@ jobs:
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 10 version: 10
- - name: Use Node.js 20
name: Use Node.js 20
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: 20.x
@@ -252,52 +212,90 @@ jobs:
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }} key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
- name: Re-link Angular cli - name: Re-link Angular cli
run: cd src-ui && pnpm link @angular/cli run: cd src-ui && pnpm link @angular/cli
- - name: Linting checks
name: Linting checks
run: cd src-ui && pnpm run lint run: cd src-ui && pnpm run lint
- - name: Run Jest unit tests
name: Run Jest unit tests
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }} run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
- - name: Upload frontend test results to Codecov
name: Run Playwright e2e tests
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
-
name: Upload frontend test results to Codecov
uses: codecov/test-results-action@v1 uses: codecov/test-results-action@v1
if: always() if: always()
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: frontend-node-${{ matrix.node-version }} flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/ directory: src-ui/
- - name: Upload frontend coverage to Codecov
name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
flags: frontend-node-${{ matrix.node-version }} flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/coverage/ directory: src-ui/coverage/
tests-frontend-e2e:
frontend-bundle-analysis: name: "Frontend E2E Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
name: "Frontend Bundle Analysis"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: needs:
- tests-frontend - install-frontend-dependencies
strategy:
fail-fast: false
matrix:
node-version: [20.x]
shard-index: [1, 2]
shard-count: [2]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- - name: Install pnpm
name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
with: with:
version: 10 version: 10
- - name: Use Node.js 20
name: Use Node.js 20
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: 20.x
cache: 'pnpm' cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml' cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- - name: Cache frontend dependencies
name: Cache frontend dependencies id: cache-frontend-deps
uses: actions/cache@v4
with:
path: |
~/.pnpm-store
~/.cache
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
- name: Re-link Angular cli
run: cd src-ui && pnpm link @angular/cli
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-playwright-
- name: Install Playwright system dependencies
run: npx playwright install-deps
- name: Install dependencies
run: cd src-ui && pnpm install --no-frozen-lockfile
- name: Install Playwright
run: cd src-ui && pnpm exec playwright install
- name: Run Playwright e2e tests
run: cd src-ui && pnpm exec playwright test --shard ${{ matrix.shard-index }}/${{ matrix.shard-count }}
frontend-bundle-analysis:
name: "Frontend Bundle Analysis"
runs-on: ubuntu-24.04
needs:
- tests-frontend
- tests-frontend-e2e
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
id: cache-frontend-deps id: cache-frontend-deps
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
@@ -305,15 +303,12 @@ jobs:
~/.pnpm-store ~/.pnpm-store
~/.cache ~/.cache
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/package-lock.json') }} key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/package-lock.json') }}
- - name: Re-link Angular cli
name: Re-link Angular cli
run: cd src-ui && pnpm link @angular/cli run: cd src-ui && pnpm link @angular/cli
- - name: Build frontend and upload analysis
name: Build frontend and upload analysis
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: cd src-ui && pnpm run build --configuration=production run: cd src-ui && pnpm run build --configuration=production
build-docker-image: build-docker-image:
name: Build Docker image for ${{ github.ref_name }} name: Build Docker image for ${{ github.ref_name }}
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -324,9 +319,9 @@ jobs:
needs: needs:
- tests-backend - tests-backend
- tests-frontend - tests-frontend
- tests-frontend-e2e
steps: steps:
- - name: Check pushing to Docker Hub
name: Check pushing to Docker Hub
id: push-other-places id: push-other-places
# Only push to Dockerhub from the main repo AND the ref is either: # Only push to Dockerhub from the main repo AND the ref is either:
# main # main
@@ -342,15 +337,13 @@ jobs:
echo "Not pushing to DockerHub" echo "Not pushing to DockerHub"
echo "enable=false" >> $GITHUB_OUTPUT echo "enable=false" >> $GITHUB_OUTPUT
fi fi
- - name: Set ghcr repository name
name: Set ghcr repository name
id: set-ghcr-repository id: set-ghcr-repository
run: | run: |
ghcr_name=$(echo "${{ github.repository }}" | awk '{ print tolower($0) }') ghcr_name=$(echo "${{ github.repository }}" | awk '{ print tolower($0) }')
echo "Name is ${ghcr_name}" echo "Name is ${ghcr_name}"
echo "ghcr-repository=${ghcr_name}" >> $GITHUB_OUTPUT echo "ghcr-repository=${ghcr_name}" >> $GITHUB_OUTPUT
- - name: Gather Docker metadata
name: Gather Docker metadata
id: docker-meta id: docker-meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5
with: with:
@@ -365,37 +358,31 @@ jobs:
# For a tag x.y.z or vX.Y.Z, output an x.y.z and x.y image tag # For a tag x.y.z or vX.Y.Z, output an x.y.z and x.y image tag
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}} type=semver,pattern={{major}}.{{minor}}
- - name: Checkout
name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
# If https://github.com/docker/buildx/issues/1044 is resolved, # If https://github.com/docker/buildx/issues/1044 is resolved,
# the append input with a native arm64 arch could be used to # the append input with a native arm64 arch could be used to
# significantly speed up building # significantly speed up building
- - name: Set up Docker Buildx
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- - name: Set up QEMU
name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
with: with:
platforms: arm64 platforms: arm64
- - name: Login to GitHub Container Registry
name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- - name: Login to Docker Hub
name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
# Don't attempt to login if not pushing to Docker Hub # Don't attempt to login if not pushing to Docker Hub
if: steps.push-other-places.outputs.enable == 'true' if: steps.push-other-places.outputs.enable == 'true'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Login to Quay.io
name: Login to Quay.io
uses: docker/login-action@v3 uses: docker/login-action@v3
# Don't attempt to login if not pushing to Quay.io # Don't attempt to login if not pushing to Quay.io
if: steps.push-other-places.outputs.enable == 'true' if: steps.push-other-places.outputs.enable == 'true'
@@ -403,8 +390,7 @@ jobs:
registry: quay.io registry: quay.io
username: ${{ secrets.QUAY_USERNAME }} username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_ROBOT_TOKEN }} password: ${{ secrets.QUAY_ROBOT_TOKEN }}
- - name: Build and push
name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
@@ -422,23 +408,19 @@ jobs:
type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:dev type=registry,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:dev
cache-to: | cache-to: |
type=registry,mode=max,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }} type=registry,mode=max,ref=ghcr.io/${{ steps.set-ghcr-repository.outputs.ghcr-repository }}/builder/cache/app:${{ github.ref_name }}
- - name: Inspect image
name: Inspect image
run: | run: |
docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }} docker buildx imagetools inspect ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
- - name: Export frontend artifact from docker
name: Export frontend artifact from docker
run: | run: |
docker create --name frontend-extract ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }} docker create --name frontend-extract ${{ fromJSON(steps.docker-meta.outputs.json).tags[0] }}
docker cp frontend-extract:/usr/src/paperless/src/documents/static/frontend src/documents/static/frontend/ docker cp frontend-extract:/usr/src/paperless/src/documents/static/frontend src/documents/static/frontend/
- - name: Upload frontend artifact
name: Upload frontend artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: frontend-compiled name: frontend-compiled
path: src/documents/static/frontend/ path: src/documents/static/frontend/
retention-days: 7 retention-days: 7
build-release: build-release:
name: "Build Release" name: "Build Release"
needs: needs:
@@ -446,63 +428,52 @@ jobs:
- documentation - documentation
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- - name: Checkout
name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- - name: Set up Python
name: Set up Python
id: setup-python id: setup-python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- - name: Install uv
name: Install uv uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v5
with: with:
version: ${{ env.DEFAULT_UV_VERSION }} version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true enable-cache: true
python-version: ${{ steps.setup-python.outputs.python-version }} python-version: ${{ steps.setup-python.outputs.python-version }}
- - name: Install Python dependencies
name: Install Python dependencies
run: | run: |
uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen uv sync --python ${{ steps.setup-python.outputs.python-version }} --dev --frozen
- - name: Install system dependencies
name: Install system dependencies
run: | run: |
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends gettext liblept5 sudo apt-get install -qq --no-install-recommends gettext liblept5
- - name: Download frontend artifact
name: Download frontend artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: frontend-compiled name: frontend-compiled
path: src/documents/static/frontend/ path: src/documents/static/frontend/
- - name: Download documentation artifact
name: Download documentation artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: documentation name: documentation
path: docs/_build/html/ path: docs/_build/html/
- - name: Generate requirements file
name: Generate requirements file
run: | run: |
uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt uv export --quiet --no-dev --all-extras --format requirements-txt --output-file requirements.txt
- - name: Compile messages
name: Compile messages
run: | run: |
cd src/ cd src/
uv run \ uv run \
--python ${{ steps.setup-python.outputs.python-version }} \ --python ${{ steps.setup-python.outputs.python-version }} \
manage.py compilemessages manage.py compilemessages
- - name: Collect static files
name: Collect static files
run: | run: |
cd src/ cd src/
uv run \ uv run \
--python ${{ steps.setup-python.outputs.python-version }} \ --python ${{ steps.setup-python.outputs.python-version }} \
manage.py collectstatic --no-input manage.py collectstatic --no-input
- - name: Move files
name: Move files
run: | run: |
echo "Making dist folders" echo "Making dist folders"
for directory in dist \ for directory in dist \
@@ -539,21 +510,18 @@ jobs:
cp --recursive docs/_build/html/ dist/paperless-ngx/docs cp --recursive docs/_build/html/ dist/paperless-ngx/docs
mv --verbose static dist/paperless-ngx mv --verbose static dist/paperless-ngx
- - name: Make release package
name: Make release package
run: | run: |
echo "Creating release archive" echo "Creating release archive"
cd dist cd dist
sudo chown -R 1000:1000 paperless-ngx/ sudo chown -R 1000:1000 paperless-ngx/
tar -cJf paperless-ngx.tar.xz paperless-ngx/ tar -cJf paperless-ngx.tar.xz paperless-ngx/
- - name: Upload release artifact
name: Upload release artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: release name: release
path: dist/paperless-ngx.tar.xz path: dist/paperless-ngx.tar.xz
retention-days: 7 retention-days: 7
publish-release: publish-release:
name: "Publish Release" name: "Publish Release"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -565,14 +533,12 @@ jobs:
- build-release - build-release
if: github.ref_type == 'tag' && (startsWith(github.ref_name, 'v') || contains(github.ref_name, '-beta.rc')) if: github.ref_type == 'tag' && (startsWith(github.ref_name, 'v') || contains(github.ref_name, '-beta.rc'))
steps: steps:
- - name: Download release artifact
name: Download release artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: release name: release
path: ./ path: ./
- - name: Get version
name: Get version
id: get_version id: get_version
run: | run: |
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
@@ -581,8 +547,7 @@ jobs:
else else
echo "prerelease=false" >> $GITHUB_OUTPUT echo "prerelease=false" >> $GITHUB_OUTPUT
fi fi
- - name: Create Release and Changelog
name: Create Release and Changelog
id: create-release id: create-release
uses: release-drafter/release-drafter@v6 uses: release-drafter/release-drafter@v6
with: with:
@@ -593,8 +558,7 @@ jobs:
publish: true # ensures release is not marked as draft publish: true # ensures release is not marked as draft
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Upload release archive
name: Upload release archive
id: upload-release-asset id: upload-release-asset
uses: shogo82148/actions-upload-release-asset@v1 uses: shogo82148/actions-upload-release-asset@v1
with: with:
@@ -603,7 +567,6 @@ jobs:
asset_path: ./paperless-ngx.tar.xz asset_path: ./paperless-ngx.tar.xz
asset_name: paperless-ngx-${{ steps.get_version.outputs.version }}.tar.xz asset_name: paperless-ngx-${{ steps.get_version.outputs.version }}.tar.xz
asset_content_type: application/x-xz asset_content_type: application/x-xz
append-changelog: append-changelog:
name: "Append Changelog" name: "Append Changelog"
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -611,26 +574,22 @@ jobs:
- publish-release - publish-release
if: needs.publish-release.outputs.prerelease == 'false' if: needs.publish-release.outputs.prerelease == 'false'
steps: steps:
- - name: Checkout
name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
ref: main ref: main
- - name: Set up Python
name: Set up Python
id: setup-python id: setup-python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- - name: Install uv
name: Install uv uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v5
with: with:
version: ${{ env.DEFAULT_UV_VERSION }} version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true enable-cache: true
python-version: ${{ env.DEFAULT_PYTHON_VERSION }} python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- - name: Append Changelog to docs
name: Append Changelog to docs
id: append-Changelog id: append-Changelog
working-directory: docs working-directory: docs
run: | run: |
@@ -652,8 +611,7 @@ jobs:
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA" git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
git push origin ${{ needs.publish-release.outputs.version }}-changelog git push origin ${{ needs.publish-release.outputs.version }}-changelog
- - name: Create Pull Request
name: Create Pull Request
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
script: | script: |

View File

@@ -6,17 +6,14 @@
# This workflow will not trigger runs on forked repos. # This workflow will not trigger runs on forked repos.
name: Cleanup Image Tags name: Cleanup Image Tags
on: on:
delete: delete:
push: push:
paths: paths:
- ".github/workflows/cleanup-tags.yml" - ".github/workflows/cleanup-tags.yml"
concurrency: concurrency:
group: registry-tags-cleanup group: registry-tags-cleanup
cancel-in-progress: false cancel-in-progress: false
jobs: jobs:
cleanup-images: cleanup-images:
name: Cleanup Image Tags for ${{ matrix.primary-name }} name: Cleanup Image Tags for ${{ matrix.primary-name }}
@@ -30,8 +27,7 @@ jobs:
# Requires a personal access token with the OAuth scope delete:packages # Requires a personal access token with the OAuth scope delete:packages
TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }} TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }}
steps: steps:
- - name: Clean temporary images
name: Clean temporary images
if: "${{ env.TOKEN != '' }}" if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.10.0 uses: stumpylog/image-cleaner-action/ephemeral@v0.10.0
with: with:
@@ -43,7 +39,6 @@ jobs:
repo_name: "paperless-ngx" repo_name: "paperless-ngx"
match_regex: "(feature|fix)" match_regex: "(feature|fix)"
do_delete: "true" do_delete: "true"
cleanup-untagged-images: cleanup-untagged-images:
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }} name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
@@ -58,8 +53,7 @@ jobs:
# Requires a personal access token with the OAuth scope delete:packages # Requires a personal access token with the OAuth scope delete:packages
TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }} TOKEN: ${{ secrets.GHA_CONTAINER_DELETE_TOKEN }}
steps: steps:
- - name: Clean untagged images
name: Clean untagged images
if: "${{ env.TOKEN != '' }}" if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.10.0 uses: stumpylog/image-cleaner-action/untagged@v0.10.0
with: with:

View File

@@ -10,16 +10,14 @@
# supported CodeQL languages. # supported CodeQL languages.
# #
name: "CodeQL" name: "CodeQL"
on: on:
push: push:
branches: [ main, dev ] branches: [main, dev]
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ dev ] branches: [dev]
schedule: schedule:
- cron: '28 13 * * 5' - cron: '28 13 * * 5'
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@@ -28,27 +26,23 @@ jobs:
actions: read actions: read
contents: read contents: read
security-events: write security-events: write
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'javascript', 'python' ] language: ['javascript', 'python']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support # Learn more about CodeQL language support at https://git.io/codeql-language-support
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
# Initializes the CodeQL tools for scanning. - name: Initialize CodeQL
- name: Initialize CodeQL uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v3 with:
with: languages: ${{ matrix.language }}
languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file.
# If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file.
# By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file.
# Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main
# queries: ./path/to/local/query, your-org/your-repo/queries@main - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View File

@@ -1,35 +1,30 @@
name: Crowdin Action name: Crowdin Action
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: '2 */12 * * *' - cron: '2 */12 * * *'
push: push:
paths: [ paths: ['src/locale/**', 'src-ui/messages.xlf', 'src-ui/src/locale/**']
'src/locale/**', branches: [dev]
'src-ui/messages.xlf',
'src-ui/src/locale/**'
]
branches: [ dev ]
jobs: jobs:
synchronize-with-crowdin: synchronize-with-crowdin:
name: Crowdin Sync name: Crowdin Sync
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: crowdin action with:
uses: crowdin/github-action@v2 token: ${{ secrets.PNGX_BOT_PAT }}
with: - name: crowdin action
upload_translations: false uses: crowdin/github-action@v2
download_translations: true with:
crowdin_branch_name: 'dev' upload_translations: false
localization_branch_name: l10n_dev download_translations: true
pull_request_labels: 'skip-changelog, translation' crowdin_branch_name: 'dev'
env: localization_branch_name: l10n_dev
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pull_request_labels: 'skip-changelog, translation'
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} env:
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

112
.github/workflows/pr-bot.yml vendored Normal file
View File

@@ -0,0 +1,112 @@
name: PR Bot
on:
pull_request_target:
types: [opened]
permissions:
contents: read
pull-requests: write
jobs:
pr-bot:
name: Automated PR Bot
runs-on: ubuntu-latest
steps:
- name: Label PR by file path or branch name
# see .github/labeler.yml for the labeler config
uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Label by size
uses: Gascon1/pr-size-labeler@v1.3.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xs_label: 'small-change'
xs_diff: '9'
s_label: 'non-trivial'
s_diff: '99999'
fail_if_xl: 'false'
excluded_files: /\.lock$/ /\.txt$/ ^src-ui/pnpm-lock\.yaml$ ^src-ui/messages\.xlf$ ^src/locale/en_US/LC_MESSAGES/django\.po$
- name: Label by PR title
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const title = pr.title.toLowerCase();
const labels = [];
if (/^(fix|bugfix)/i.test(title)) {
labels.push('bug');
} else if (/^feature/i.test(title)) {
labels.push('enhancement');
} else if (!/^(dependabot)/i.test(title)) {
labels.push('enhancement'); // Default fallback
}
if (labels.length) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels,
});
core.info(`Added labels based on title: ${labels.join(', ')}`);
}
- name: Label bot-generated PRs
if: ${{ contains(github.actor, 'dependabot') || contains(github.actor, 'crowdin-bot') }}
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const user = pr.user.login.toLowerCase();
const labels = [];
if (user.includes('dependabot')) {
labels.push('dependencies');
}
if (user.includes('crowdin-bot')) {
labels.push('translation', 'skip-changelog');
}
if (labels.length) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels,
});
}
- name: Welcome comment
if: ${{ !contains(github.actor, 'bot') }}
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const user = pr.user.login;
const { data: members } = await github.rest.orgs.listMembers({
org: 'paperless-ngx',
});
const memberLogins = members.map(m => m.login.toLowerCase());
if (memberLogins.includes(user.toLowerCase())) {
core.info('Skipping comment: user is org member');
return;
}
const body =
"Hello @" + user + ",\n\n" +
"Thank you very much for submitting this PR to us!\n\n" +
"This is what will happen next:\n\n" +
"1. CI tests will run against your PR to ensure quality and consistency.\n" +
"2. Next, human contributors from paperless-ngx review your changes.\n" +
"3. Please address any issues that come up during the review as soon as you are able to.\n" +
"4. If accepted, your pull request will be merged into the `dev` branch and changes there will be tested further.\n" +
"5. Eventually, changes from you and other contributors will be merged into `main` and a new release will be made.\n\n" +
"You'll be hearing from us soon, and thank you again for contributing to our project.";
await github.rest.issues.createComment({
issue_number: pr.number,
owner: context.repo.owner,
repo: context.repo.repo,
body,
});

View File

@@ -1,5 +1,4 @@
name: Project Automations name: Project Automations
on: on:
pull_request_target: #_target allows access to secrets pull_request_target: #_target allows access to secrets
types: types:
@@ -8,10 +7,8 @@ on:
branches: branches:
- main - main
- dev - dev
permissions: permissions:
contents: read contents: read
jobs: jobs:
pr_opened_or_reopened: pr_opened_or_reopened:
name: pr_opened_or_reopened name: pr_opened_or_reopened

View File

@@ -1,18 +1,14 @@
name: 'Repository Maintenance' name: 'Repository Maintenance'
on: on:
schedule: schedule:
- cron: '0 3 * * *' - cron: '0 3 * * *'
workflow_dispatch: workflow_dispatch:
permissions: permissions:
issues: write issues: write
pull-requests: write pull-requests: write
discussions: write discussions: write
concurrency: concurrency:
group: lock group: lock
jobs: jobs:
stale: stale:
name: 'Stale' name: 'Stale'
@@ -27,9 +23,8 @@ jobs:
stale-issue-label: stale stale-issue-label: stale
stale-pr-label: stale stale-pr-label: stale
stale-issue-message: > stale-issue-message: >
This issue has been automatically marked as stale because it has not had This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
lock-threads: lock-threads:
name: 'Lock Old Threads' name: 'Lock Old Threads'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'
@@ -42,20 +37,14 @@ jobs:
discussion-inactive-days: '30' discussion-inactive-days: '30'
log-output: true log-output: true
issue-comment: > issue-comment: >
This issue has been automatically locked since there This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
has not been any recent activity after it was closed.
Please open a new discussion or issue for related concerns.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
pr-comment: > pr-comment: >
This pull request has been automatically locked since there This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion or issue for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
has not been any recent activity after it was closed.
Please open a new discussion or issue for related concerns.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
discussion-comment: > discussion-comment: >
This discussion has been automatically locked since there This discussion has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
has not been any recent activity after it was closed.
Please open a new discussion for related concerns.
See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
close-answered-discussions: close-answered-discussions:
name: 'Close Answered Discussions' name: 'Close Answered Discussions'
if: github.repository_owner == 'paperless-ngx' if: github.repository_owner == 'paperless-ngx'

69
.github/workflows/translate-strings.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Generate Translation Strings
on:
push:
branches:
- dev
jobs:
generate-translate-strings:
name: Generate Translation Strings
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.PNGX_BOT_PAT }}
ref: ${{ github.head_ref }}
- name: Set up Python
id: setup-python
uses: actions/setup-python@v5
- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends gettext
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Install backend python dependencies
run: |
uv sync \
--group dev \
--frozen
- name: Generate backend translation strings
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
id: cache-frontend-deps
uses: actions/cache@v4
with:
path: |
~/.pnpm-store
~/.cache
key: ${{ runner.os }}-frontenddeps-${{ hashFiles('src-ui/pnpm-lock.yaml') }}
- name: Install frontend dependencies
if: steps.cache-frontend-deps.outputs.cache-hit != 'true'
run: cd src-ui && pnpm install
- name: Re-link Angular cli
run: cd src-ui && pnpm link @angular/cli
- name: Generate frontend translation strings
run: |
cd src-ui
pnpm run ng extract-i18n
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
file_pattern: 'src-ui/messages.xlf src/locale/en_US/LC_MESSAGES/django.po'
commit_message: "Auto translate strings"
commit_user_name: "GitHub Actions"
commit_author: "GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>"

View File

@@ -76,3 +76,8 @@ repos:
rev: "v0.10.0.1" rev: "v0.10.0.1"
hooks: hooks:
- id: shellcheck - id: shellcheck
- repo: https://github.com/google/yamlfmt
rev: v0.14.0
hooks:
- id: yamlfmt
exclude: "^src-ui/pnpm-lock.yaml"

View File

@@ -81,7 +81,7 @@ Some notes about translation:
If a language has already been added, and you would like to contribute new translations or change existing translations, please read the "Translation" section in the README.md file for further details on that. If a language has already been added, and you would like to contribute new translations or change existing translations, please read the "Translation" section in the README.md file for further details on that.
If you would like the project to be translated to another language, first head over to https://crwd.in/paperless-ngx to check if that language has already been enabled for translation. If you would like the project to be translated to another language, first head over to https://crowdin.com/project/paperless-ngx to check if that language has already been enabled for translation.
If not, please request the language to be added by creating an issue on GitHub. The issue should contain: If not, please request the language to be added by creating an issue on GitHub. The issue should contain:
- English name of the language (the localized name can be added on Crowdin). - English name of the language (the localized name can be added on Crowdin).

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.5-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
@@ -47,7 +47,7 @@ ENV \
ARG TARGETARCH ARG TARGETARCH
ARG TARGETVARIANT ARG TARGETVARIANT
# Lock this version # Lock this version
ARG S6_OVERLAY_VERSION=3.2.0.2 ARG S6_OVERLAY_VERSION=3.2.1.0
ARG S6_BUILD_TIME_PKGS="curl \ ARG S6_BUILD_TIME_PKGS="curl \
xz-utils" xz-utils"
@@ -239,6 +239,7 @@ COPY --from=compile-frontend --chown=1000:1000 /src/src/documents/static/fronten
# add users, setup scripts # add users, setup scripts
# Mount the compiled frontend to expected location # Mount the compiled frontend to expected location
RUN set -eux \ RUN set -eux \
&& sed -i '1s|^#!/usr/bin/env python3|#!/command/with-contenv python3|' manage.py \
&& echo "Setting up user/group" \ && echo "Setting up user/group" \
&& addgroup --gid 1000 paperless \ && addgroup --gid 1000 paperless \
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \ && useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \

View File

@@ -83,7 +83,7 @@ People interested in continuing the work on paperless-ngx are encouraged to reac
## Translation ## Translation
Paperless-ngx is available in many languages that are coordinated on Crowdin. If you want to help out by translating paperless-ngx into your language, please head over to https://crwd.in/paperless-ngx, and thank you! More details can be found in [CONTRIBUTING.md](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md#translating-paperless-ngx). Paperless-ngx is available in many languages that are coordinated on Crowdin. If you want to help out by translating paperless-ngx into your language, please head over to https://crowdin.com/project/paperless-ngx, and thank you! More details can be found in [CONTRIBUTING.md](https://github.com/paperless-ngx/paperless-ngx/blob/main/CONTRIBUTING.md#translating-paperless-ngx).
## Feature Requests ## Feature Requests

View File

@@ -5,7 +5,7 @@
services: services:
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.17 image: docker.io/gotenberg/gotenberg:8.20
hostname: gotenberg hostname: gotenberg
container_name: gotenberg container_name: gotenberg
network_mode: host network_mode: host

View File

@@ -24,19 +24,18 @@
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' # - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
# and '.env' into a folder. # and '.env' into a folder.
# - Run 'docker compose pull'. # - Run 'docker compose pull'.
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
# - Run 'docker compose up -d'. # - Run 'docker compose up -d'.
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
# documentation. # documentation.
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
db: db:
image: docker.io/library/mariadb:11 image: docker.io/library/mariadb:11
restart: unless-stopped restart: unless-stopped
@@ -48,7 +47,6 @@ services:
MARIADB_USER: paperless MARIADB_USER: paperless
MARIADB_PASSWORD: paperless MARIADB_PASSWORD: paperless
MARIADB_ROOT_PASSWORD: paperless MARIADB_ROOT_PASSWORD: paperless
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -75,9 +73,8 @@ services:
PAPERLESS_TIKA_ENABLED: 1 PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.17 image: docker.io/gotenberg/gotenberg:8.20
restart: unless-stopped restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not # The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript. # want to allow external content like tracking pixels or even javascript.
@@ -85,11 +82,9 @@ services:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:latest image: docker.io/apache/tika:latest
restart: unless-stopped restart: unless-stopped
volumes: volumes:
data: data:
media: media:

View File

@@ -20,7 +20,6 @@
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' # - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
# and '.env' into a folder. # and '.env' into a folder.
# - Run 'docker compose pull'. # - Run 'docker compose pull'.
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
# - Run 'docker compose up -d'. # - Run 'docker compose up -d'.
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
@@ -28,11 +27,10 @@
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
db: db:
image: docker.io/library/mariadb:11 image: docker.io/library/mariadb:11
restart: unless-stopped restart: unless-stopped
@@ -44,7 +42,6 @@ services:
MARIADB_USER: paperless MARIADB_USER: paperless
MARIADB_PASSWORD: paperless MARIADB_PASSWORD: paperless
MARIADB_ROOT_PASSWORD: paperless MARIADB_ROOT_PASSWORD: paperless
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -66,7 +63,6 @@ services:
PAPERLESS_DBUSER: paperless # only needed if non-default username PAPERLESS_DBUSER: paperless # only needed if non-default username
PAPERLESS_DBPASS: paperless # only needed if non-default password PAPERLESS_DBPASS: paperless # only needed if non-default password
PAPERLESS_DBPORT: 3306 PAPERLESS_DBPORT: 3306
volumes: volumes:
data: data:
media: media:

View File

@@ -22,21 +22,16 @@
# - Upload 'docker-compose.env' by clicking on 'Load variables from .env file' # - Upload 'docker-compose.env' by clicking on 'Load variables from .env file'
# - Modify the environment variables as needed # - Modify the environment variables as needed
# - Click 'Deploy the stack' and wait for it to be deployed # - Click 'Deploy the stack' and wait for it to be deployed
# - Open the list of containers, select paperless_webserver_1
# - Click 'Console' and then 'Connect' to open the command line inside the container
# - Run 'python3 manage.py createsuperuser' to create a user
# - Exit the console
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
# documentation. # documentation.
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
db: db:
image: docker.io/library/postgres:17 image: docker.io/library/postgres:17
restart: unless-stopped restart: unless-stopped
@@ -46,7 +41,6 @@ services:
POSTGRES_DB: paperless POSTGRES_DB: paperless
POSTGRES_USER: paperless POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless POSTGRES_PASSWORD: paperless
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -65,7 +59,6 @@ services:
PAPERLESS_DBHOST: db PAPERLESS_DBHOST: db
env_file: env_file:
- stack.env - stack.env
volumes: volumes:
data: data:
media: media:

View File

@@ -24,7 +24,6 @@
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' # - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
# and '.env' into a folder. # and '.env' into a folder.
# - Run 'docker compose pull'. # - Run 'docker compose pull'.
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
# - Run 'docker compose up -d'. # - Run 'docker compose up -d'.
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
@@ -32,11 +31,10 @@
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
db: db:
image: docker.io/library/postgres:17 image: docker.io/library/postgres:17
restart: unless-stopped restart: unless-stopped
@@ -46,7 +44,6 @@ services:
POSTGRES_DB: paperless POSTGRES_DB: paperless
POSTGRES_USER: paperless POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless POSTGRES_PASSWORD: paperless
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -69,22 +66,18 @@ services:
PAPERLESS_TIKA_ENABLED: 1 PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.17 image: docker.io/gotenberg/gotenberg:8.20
restart: unless-stopped restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not # The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript. # want to allow external content like tracking pixels or even javascript.
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:latest image: docker.io/apache/tika:latest
restart: unless-stopped restart: unless-stopped
volumes: volumes:
data: data:
media: media:

View File

@@ -20,7 +20,6 @@
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' # - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
# and '.env' into a folder. # and '.env' into a folder.
# - Run 'docker compose pull'. # - Run 'docker compose pull'.
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
# - Run 'docker compose up -d'. # - Run 'docker compose up -d'.
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
@@ -28,11 +27,10 @@
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
db: db:
image: docker.io/library/postgres:17 image: docker.io/library/postgres:17
restart: unless-stopped restart: unless-stopped
@@ -42,7 +40,6 @@ services:
POSTGRES_DB: paperless POSTGRES_DB: paperless
POSTGRES_USER: paperless POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless POSTGRES_PASSWORD: paperless
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -60,7 +57,6 @@ services:
environment: environment:
PAPERLESS_REDIS: redis://broker:6379 PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db PAPERLESS_DBHOST: db
volumes: volumes:
data: data:
media: media:

View File

@@ -24,7 +24,6 @@
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' # - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
# and '.env' into a folder. # and '.env' into a folder.
# - Run 'docker compose pull'. # - Run 'docker compose pull'.
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
# - Run 'docker compose up -d'. # - Run 'docker compose up -d'.
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
@@ -32,11 +31,10 @@
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -57,22 +55,18 @@ services:
PAPERLESS_TIKA_ENABLED: 1 PAPERLESS_TIKA_ENABLED: 1
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg: gotenberg:
image: docker.io/gotenberg/gotenberg:8.17 image: docker.io/gotenberg/gotenberg:8.20
restart: unless-stopped restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not # The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript. # want to allow external content like tracking pixels or even javascript.
command: command:
- "gotenberg" - "gotenberg"
- "--chromium-disable-javascript=true" - "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*" - "--chromium-allow-list=file:///tmp/.*"
tika: tika:
image: docker.io/apache/tika:latest image: docker.io/apache/tika:latest
restart: unless-stopped restart: unless-stopped
volumes: volumes:
data: data:
media: media:

View File

@@ -17,7 +17,6 @@
# - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env' # - Copy this file as 'docker-compose.yml' and the files 'docker-compose.env'
# and '.env' into a folder. # and '.env' into a folder.
# - Run 'docker compose pull'. # - Run 'docker compose pull'.
# - Run 'docker compose run --rm webserver createsuperuser' to create a user.
# - Run 'docker compose up -d'. # - Run 'docker compose up -d'.
# #
# For more extensive installation and update instructions, refer to the # For more extensive installation and update instructions, refer to the
@@ -25,11 +24,10 @@
services: services:
broker: broker:
image: docker.io/library/redis:7 image: docker.io/library/redis:8
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- redisdata:/data - redisdata:/data
webserver: webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped restart: unless-stopped
@@ -45,7 +43,6 @@ services:
env_file: docker-compose.env env_file: docker-compose.env
environment: environment:
PAPERLESS_REDIS: redis://broker:6379 PAPERLESS_REDIS: redis://broker:6379
volumes: volumes:
data: data:
media: media:

View File

@@ -1,179 +0,0 @@
#!/usr/bin/env bash
set -e
# Source: https://github.com/sameersbn/docker-gitlab/
map_uidgid() {
local -r usermap_original_uid=$(id -u paperless)
local -r usermap_original_gid=$(id -g paperless)
local -r usermap_new_uid=${USERMAP_UID:-$usermap_original_uid}
local -r usermap_new_gid=${USERMAP_GID:-${usermap_original_gid:-$usermap_new_uid}}
if [[ ${usermap_new_uid} != "${usermap_original_uid}" || ${usermap_new_gid} != "${usermap_original_gid}" ]]; then
echo "Mapping UID and GID for paperless:paperless to $usermap_new_uid:$usermap_new_gid"
usermod --non-unique --uid "${usermap_new_uid}" paperless
groupmod --non-unique --gid "${usermap_new_gid}" paperless
fi
}
map_folders() {
# Export these so they can be used in docker-prepare.sh
export DATA_DIR="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}"
export MEDIA_ROOT_DIR="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}"
export CONSUME_DIR="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}"
}
custom_container_init() {
# Mostly borrowed from the LinuxServer.io base image
# https://github.com/linuxserver/docker-baseimage-ubuntu/tree/bionic/root/etc/cont-init.d
local -r custom_script_dir="/custom-cont-init.d"
# Tamper checking.
# Don't run files which are owned by anyone except root
# Don't run files which are writeable by others
if [ -d "${custom_script_dir}" ]; then
if [ -n "$(/usr/bin/find "${custom_script_dir}" -maxdepth 1 ! -user root)" ]; then
echo "**** Potential tampering with custom scripts detected ****"
echo "**** The folder '${custom_script_dir}' must be owned by root ****"
return 0
fi
if [ -n "$(/usr/bin/find "${custom_script_dir}" -maxdepth 1 -perm -o+w)" ]; then
echo "**** The folder '${custom_script_dir}' or some of contents have write permissions for others, which is a security risk. ****"
echo "**** Please review the permissions and their contents to make sure they are owned by root, and can only be modified by root. ****"
return 0
fi
# Make sure custom init directory has files in it
if [ -n "$(/bin/ls --almost-all "${custom_script_dir}" 2>/dev/null)" ]; then
echo "[custom-init] files found in ${custom_script_dir} executing"
# Loop over files in the directory
for SCRIPT in "${custom_script_dir}"/*; do
NAME="$(basename "${SCRIPT}")"
if [ -f "${SCRIPT}" ]; then
echo "[custom-init] ${NAME}: executing..."
/bin/bash "${SCRIPT}"
echo "[custom-init] ${NAME}: exited $?"
elif [ ! -f "${SCRIPT}" ]; then
echo "[custom-init] ${NAME}: is not a file"
fi
done
else
echo "[custom-init] no custom files found exiting..."
fi
fi
}
initialize() {
# Setup environment from secrets before anything else
# Check for a version of this var with _FILE appended
# and convert the contents to the env var value
# Source it so export is persistent
# shellcheck disable=SC1091
source /sbin/env-from-file.sh
# Change the user and group IDs if needed
map_uidgid
# Check for overrides of certain folders
map_folders
local -r export_dir="/usr/src/paperless/export"
for dir in \
"${export_dir}" \
"${DATA_DIR}" "${DATA_DIR}/index" \
"${MEDIA_ROOT_DIR}" "${MEDIA_ROOT_DIR}/documents" "${MEDIA_ROOT_DIR}/documents/originals" "${MEDIA_ROOT_DIR}/documents/thumbnails" \
"${CONSUME_DIR}"; do
if [[ ! -d "${dir}" ]]; then
echo "Creating directory ${dir}"
mkdir --parents --verbose "${dir}"
fi
done
local -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}"
echo "Creating directory scratch directory ${tmp_dir}"
mkdir --parents --verbose "${tmp_dir}"
set +e
echo "Adjusting permissions of paperless files. This may take a while."
chown -R paperless:paperless "${tmp_dir}"
for dir in \
"${export_dir}" \
"${DATA_DIR}" \
"${MEDIA_ROOT_DIR}" \
"${CONSUME_DIR}"; do
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown --changes paperless:paperless {} +
done
set -e
"${gosu_cmd[@]}" /sbin/docker-prepare.sh
# Leave this last thing
custom_container_init
}
install_languages() {
echo "Installing languages..."
read -ra langs <<<"$1"
# Check that it is not empty
if [ ${#langs[@]} -eq 0 ]; then
return
fi
# Build list of packages to install
to_install=()
for lang in "${langs[@]}"; do
pkg="tesseract-ocr-$lang"
if dpkg --status "$pkg" &>/dev/null; then
echo "Package $pkg already installed!"
continue
else
to_install+=("$pkg")
fi
done
# Use apt only when we install packages
if [ ${#to_install[@]} -gt 0 ]; then
apt-get update
for pkg in "${to_install[@]}"; do
if ! apt-cache show "$pkg" &>/dev/null; then
echo "Skipped $pkg: Package not found! :("
continue
fi
echo "Installing package $pkg..."
if ! apt-get --assume-yes install "$pkg" &>/dev/null; then
echo "Could not install $pkg"
exit 1
fi
done
fi
}
echo "Paperless-ngx docker container starting..."
gosu_cmd=(gosu paperless)
if [ "$(id --user)" == "$(id --user paperless)" ]; then
gosu_cmd=()
fi
# Install additional languages if specified
if [[ -n "$PAPERLESS_OCR_LANGUAGES" ]]; then
install_languages "$PAPERLESS_OCR_LANGUAGES"
fi
initialize
if [[ "$1" != "/"* ]]; then
echo Executing management command "$@"
exec "${gosu_cmd[@]}" python3 manage.py "$@"
else
echo Executing "$@"
exec "$@"
fi

View File

@@ -18,9 +18,10 @@ for command in decrypt_documents \
document_fuzzy_match \ document_fuzzy_match \
manage_superuser \ manage_superuser \
convert_mariadb_uuid \ convert_mariadb_uuid \
prune_audit_logs; prune_audit_logs \
createsuperuser;
do do
echo "installing $command..." echo "installing $command..."
sed "s/management_command/$command/g" management_script.sh >"$PWD/rootfs/usr/local/bin/$command" sed "s/management_command/$command/g" management_script.sh >"$PWD/rootfs/usr/local/bin/$command"
chmod +x "$PWD/rootfs/usr/local/bin/$command" chmod u=rwx,g=rwx,o=rx "$PWD/rootfs/usr/local/bin/$command"
done done

View File

@@ -9,7 +9,7 @@ if find /run/s6/container_environment/*"_FILE" -maxdepth 1 > /dev/null 2>&1; the
for FILENAME in /run/s6/container_environment/*; do for FILENAME in /run/s6/container_environment/*; do
if [[ "${FILENAME##*/}" == PAPERLESS_*_FILE ]]; then if [[ "${FILENAME##*/}" == PAPERLESS_*_FILE ]]; then
# This should have been named different.. # This should have been named different..
if [[ ${FILENAME} == "PAPERLESS_OCR_SKIP_ARCHIVE_FILE" || ${FILENAME} == "PAPERLESS_MODEL_FILE" ]]; then if [[ "${FILENAME##*/}" == "PAPERLESS_OCR_SKIP_ARCHIVE_FILE" || "${FILENAME##*/}" == "PAPERLESS_MODEL_FILE" ]]; then
continue continue
fi fi
SECRETFILE=$(cat "${FILENAME}") SECRETFILE=$(cat "${FILENAME}")
@@ -17,6 +17,9 @@ if find /run/s6/container_environment/*"_FILE" -maxdepth 1 > /dev/null 2>&1; the
if [[ -f ${SECRETFILE} ]]; then if [[ -f ${SECRETFILE} ]]; then
# Trim off trailing _FILE # Trim off trailing _FILE
FILESTRIP=${FILENAME//_FILE/} FILESTRIP=${FILENAME//_FILE/}
if [[ $(tail -n1 "${SECRETFILE}" | wc -l) != 0 ]]; then
echo "${log_prefix} Your secret: ${FILENAME##*/} contains a trailing newline and may not work as expected"
fi
# Set environment variable # Set environment variable
cat "${SECRETFILE}" > "${FILESTRIP}" cat "${SECRETFILE}" > "${FILESTRIP}"
echo "${log_prefix} ${FILESTRIP##*/} set from ${FILENAME##*/}" echo "${log_prefix} ${FILESTRIP##*/} set from ${FILENAME##*/}"

View File

@@ -9,25 +9,57 @@ declare -r media_root_dir="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}"
declare -r consume_dir="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}" declare -r consume_dir="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}"
declare -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}" declare -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}"
echo "${log_prefix} Checking for folder existence" declare -r main_dirs=(
"${export_dir}"
"${data_dir}"
"${media_root_dir}"
"${consume_dir}"
"${tmp_dir}"
)
for dir in \ declare -r extra_dirs=(
"${export_dir}" \ "${main_dirs[@]}"
"${data_dir}" "${data_dir}/index" \ "${data_dir}/index"
"${media_root_dir}" "${media_root_dir}/documents" "${media_root_dir}/documents/originals" "${media_root_dir}/documents/thumbnails" \ "${media_root_dir}/documents"
"${consume_dir}" \ "${media_root_dir}/documents/originals"
"${tmp_dir}"; do "${media_root_dir}/documents/thumbnails"
if [[ ! -d "${dir}" ]]; then )
mkdir --parents --verbose "${dir}"
fi
done
echo "${log_prefix} Adjusting file and folder permissions" if [[ -n "${USER_IS_NON_ROOT}" ]]; then
for dir in \ # Non-root mode: Create directories as current user, warn about permission issues
"${export_dir}" \ echo "${log_prefix} Running in non-root mode, checking directories"
"${data_dir}" \ current_uid=$(id --user)
"${media_root_dir}" \ current_gid=$(id --group)
"${consume_dir}" \
"${tmp_dir}"; do for dir in "${extra_dirs[@]}"; do
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown --changes paperless:paperless {} + if [[ ! -d "${dir}" ]]; then
done mkdir --parents --verbose "${dir}" || echo "${log_prefix} WARNING: Could not create ${dir} - permission denied"
fi
# Check permissions on existing directories too
if [[ -d "${dir}" && ! -w "${dir}" ]]; then
echo "${log_prefix} WARNING: No write permission to ${dir}"
fi
done
# Warn about ownership issues
for dir in "${main_dirs[@]}"; do
if [[ -d "${dir}" ]]; then
find "${dir}" -not \( -user ${current_uid} -and -group ${current_gid} \) -exec echo "${log_prefix} WARNING: Permission issue on {}: not owned by current user (${current_uid}:${current_gid})" \; 2>/dev/null || echo "${log_prefix} WARNING: Cannot check permissions on ${dir}"
fi
done
else
# Root mode: Create and fix permissions as needed
echo "${log_prefix} Running with root privileges, adjusting directories and permissions"
# First create directories
for dir in "${extra_dirs[@]}"; do
if [[ ! -d "${dir}" ]]; then
mkdir --parents --verbose "${dir}"
fi
done
# Then fix permissions on all directories
for dir in "${main_dirs[@]}"; do
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown --changes paperless:paperless {} +
done
fi

View File

@@ -1,20 +1,18 @@
#!/command/with-contenv /usr/bin/bash #!/command/with-contenv /usr/bin/bash
# shellcheck shell=bash # shellcheck shell=bash
declare -r log_prefix="[init-migrations]" declare -r log_prefix="[init-migrations]"
declare -r data_dir="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}" declare -r data_dir="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}"
( echo "${log_prefix} Apply database migrations..."
# flock is in place to prevent multiple containers from doing migrations
# simultaneously. This also ensures that the db is ready when the command
# of the current container starts.
flock 200
echo "${log_prefix} Apply database migrations..."
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then cd "${PAPERLESS_SRC_DIR}"
exec python3 manage.py migrate --skip-checks --no-input
else
exec s6-setuidgid paperless python3 manage.py migrate --skip-checks --no-input
fi
) 200>"${data_dir}/migration_lock" # The whole migrate, with flock, needs to run as the right user
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
exec s6-setlock -n "${data_dir}/migration_lock" python3 manage.py migrate --skip-checks --no-input
else
exec s6-setuidgid paperless \
s6-setlock -n "${data_dir}/migration_lock" \
python3 manage.py migrate --skip-checks --no-input
fi

View File

@@ -11,9 +11,10 @@ printf "/usr/src/paperless/src" > /var/run/s6/container_environment/PAPERLESS_SR
echo $(date +%s) > /var/run/s6/container_environment/PAPERLESS_START_TIME_S 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 -u) == $(id -u paperless) ]; 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" echo "${log_prefix} paperless-ngx docker container running under a user ($(id --user):$(id --group))"
else else
echo "${log_prefix} paperless-ngx docker container starting init as root" printf "/usr/src/paperless" > /var/run/s6/container_environment/HOME
echo "${log_prefix} paperless-ngx docker container starting init as root"
fi fi

View File

@@ -14,7 +14,7 @@ if [[ -n "${PAPERLESS_FORCE_SCRIPT_NAME}" ]]; then
fi fi
if [[ -n "${USER_IS_NON_ROOT}" ]]; then if [[ -n "${USER_IS_NON_ROOT}" ]]; then
exec granian --interface asginl --ws "paperless.asgi:application" exec granian --interface asginl --ws --loop uvloop "paperless.asgi:application"
else else
exec s6-setuidgid paperless granian --interface asginl --ws "paperless.asgi:application" exec s6-setuidgid paperless granian --interface asginl --ws --loop uvloop "paperless.asgi:application"
fi fi

View File

@@ -0,0 +1,14 @@
#!/command/with-contenv /usr/bin/bash
# shellcheck shell=bash
set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py createsuperuser "$@"
elif [[ $(id -un) == "paperless" ]]; then
python3 manage.py createsuperuser "$@"
else
echo "Unknown user."
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
@@ -565,19 +562,15 @@ document.
### Managing encryption {#encryption} ### Managing encryption {#encryption}
Documents can be stored in Paperless using GnuPG encryption.
!!! warning !!! warning
Encryption is deprecated since [paperless-ng 0.9](changelog.md#paperless-ng-090) and doesn't really Encryption was removed in [paperless-ng 0.9](changelog.md#paperless-ng-090)
provide any additional security, since you have to store the passphrase because it did not really provide any additional security, the passphrase
in a configuration file on the same system as the encrypted documents was stored in a configuration file on the same system as the documents.
for paperless to work. Furthermore, the entire text content of the Furthermore, the entire text content of the documents is stored plain in
documents is stored plain in the database, even if your documents are the database, even if your documents are encrypted. Filenames are not
encrypted. Filenames are not encrypted as well. encrypted as well. Finally, the web server provides transparent access to
your encrypted documents.
Also, the web server provides transparent access to your encrypted
documents.
Consider running paperless on an encrypted filesystem instead, which Consider running paperless on an encrypted filesystem instead, which
will then at least provide security against physical hardware theft. will then at least provide security against physical hardware theft.
@@ -633,3 +626,11 @@ entries created prior to this are not removed. This command allows you to prune
```shell ```shell
prune_audit_logs prune_audit_logs
``` ```
### Create superuser {#create-superuser}
If you need to create a superuser, use the following command:
```shell
createsuperuser
```

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

@@ -132,7 +132,7 @@ use cases:
5. Documents with a custom field "address" (text) that is empty: 5. Documents with a custom field "address" (text) that is empty:
`?custom_field_query=["OR", ["address", "isnull", true], ["address", "exact", ""]]` `?custom_field_query=["OR", [["address", "isnull", true], ["address", "exact", ""]]]`
6. Documents that don't have a field called "foo": 6. Documents that don't have a field called "foo":
@@ -270,7 +270,7 @@ The following methods are supported:
- `remove_tag` - `remove_tag`
- Requires `parameters`: `{ "tag": TAG_ID }` - Requires `parameters`: `{ "tag": TAG_ID }`
- `modify_tags` - `modify_tags`
- Requires `parameters`: `{ "add_tags": [LIST_OF_TAG_IDS] }` and / or `{ "remove_tags": [LIST_OF_TAG_IDS] }` - Requires `parameters`: `{ "add_tags": [LIST_OF_TAG_IDS] }` and `{ "remove_tags": [LIST_OF_TAG_IDS] }`
- `delete` - `delete`
- No `parameters` required - No `parameters` required
- `reprocess` - `reprocess`
@@ -413,3 +413,14 @@ Initial API version.
list of strings. When creating or updating a custom field value of a list of strings. When creating or updating a custom field value of a
document for a select type custom field, the value should be the `id` of document for a select type custom field, the value should be the `id` of
the option whereas previously was the index of the option. the option whereas previously was the index of the option.
#### Version 8
- The user field of document notes now returns a simplified user object
rather than just the user ID.
#### Version 9
- The document `created` field is now a date, not a datetime. The
`created_date` field is considered deprecated and will be removed in a
future version.

View File

@@ -1,5 +1,563 @@
# 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
### Bug Fixes
- Fix: fix created date filtering broken in 2.16.0 [@shamoon](https://github.com/shamoon) ([#9976](https://github.com/paperless-ngx/paperless-ngx/pull/9976))
### All App Changes
- Fix: fix created date filtering broken in 2.16.0 [@shamoon](https://github.com/shamoon) ([#9976](https://github.com/paperless-ngx/paperless-ngx/pull/9976))
## paperless-ngx 2.16.0
### Breaking Changes
- [BREAKING] Change: treat created as date not datetime [@shamoon](https://github.com/shamoon) ([#9793](https://github.com/paperless-ngx/paperless-ngx/pull/9793))
### Features
- Enhancement: support negative offset in scheduled workflows [@shamoon](https://github.com/shamoon) ([#9746](https://github.com/paperless-ngx/paperless-ngx/pull/9746))
- Enhancement: support heic images [@shamoon](https://github.com/shamoon) ([#9771](https://github.com/paperless-ngx/paperless-ngx/pull/9771))
- Enhancement: use patch instead of put for frontend document changes [@shamoon](https://github.com/shamoon) ([#9744](https://github.com/paperless-ngx/paperless-ngx/pull/9744))
- Fixhancement: automatically disable email verification if no smtp setup [@shamoon](https://github.com/shamoon) ([#9949](https://github.com/paperless-ngx/paperless-ngx/pull/9949))
- Fixhancement: better handle removed social apps in profile [@shamoon](https://github.com/shamoon) ([#9876](https://github.com/paperless-ngx/paperless-ngx/pull/9876))
- Enhancement: add barcode frontend config [@shamoon](https://github.com/shamoon) ([#9742](https://github.com/paperless-ngx/paperless-ngx/pull/9742))
- Enhancement: support allauth disable unknown account emails [@shamoon](https://github.com/shamoon) ([#9743](https://github.com/paperless-ngx/paperless-ngx/pull/9743))
- Fixhancement: tag plus button should add tag to doc [@shamoon](https://github.com/shamoon) ([#9762](https://github.com/paperless-ngx/paperless-ngx/pull/9762))
- Fixhancement: check more permissions for status consumer messages [@shamoon](https://github.com/shamoon) ([#9804](https://github.com/paperless-ngx/paperless-ngx/pull/9804))
### Bug Fixes
- Fix: include subpath in drf-spectacular settings if set [@shamoon](https://github.com/shamoon) ([#9738](https://github.com/paperless-ngx/paperless-ngx/pull/9738))
- Fix: handle created change with api version increment, use created only on frontend, deprecate created_date [@shamoon](https://github.com/shamoon) ([#9962](https://github.com/paperless-ngx/paperless-ngx/pull/9962))
- Fix: ignore logo file from sanity checker [@shamoon](https://github.com/shamoon) ([#9946](https://github.com/paperless-ngx/paperless-ngx/pull/9946))
- Fix: correctly handle empty user for old notes api format, fix frontend API version [@shamoon](https://github.com/shamoon) ([#9846](https://github.com/paperless-ngx/paperless-ngx/pull/9846))
- Fix: fix single select in filterable dropdowns when editing [@shamoon](https://github.com/shamoon) ([#9834](https://github.com/paperless-ngx/paperless-ngx/pull/9834))
- Fix: always update classifier task result [@shamoon](https://github.com/shamoon) ([#9817](https://github.com/paperless-ngx/paperless-ngx/pull/9817))
- Fix: fix zoom increase/decrease buttons in FF [@shamoon](https://github.com/shamoon) ([#9761](https://github.com/paperless-ngx/paperless-ngx/pull/9761))
### Maintenance
- Chore(deps): Bump astral-sh/setup-uv from 5 to 6 in the actions group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9842](https://github.com/paperless-ngx/paperless-ngx/pull/9842))
- Chore: split ci frontend e2e vs unit tests [@shamoon](https://github.com/shamoon) ([#9851](https://github.com/paperless-ngx/paperless-ngx/pull/9851))
- Chore: auto-generate translation strings [@shamoon](https://github.com/shamoon) ([#9462](https://github.com/paperless-ngx/paperless-ngx/pull/9462))
- Chore: add ymlfmt [@shamoon](https://github.com/shamoon) ([#9745](https://github.com/paperless-ngx/paperless-ngx/pull/9745))
- Chore: replace secretary with GHA [@shamoon](https://github.com/shamoon) ([#9723](https://github.com/paperless-ngx/paperless-ngx/pull/9723))
- Chore: resolve dynamic import warnings from pdfjs, again [@shamoon](https://github.com/shamoon) ([#9924](https://github.com/paperless-ngx/paperless-ngx/pull/9924))
- Fix/Chore: replace file drop package [@shamoon](https://github.com/shamoon) ([#9926](https://github.com/paperless-ngx/paperless-ngx/pull/9926))
### Dependencies
<details>
<summary>14 changes</summary>
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9921](https://github.com/paperless-ngx/paperless-ngx/pull/9921))
- docker-compose(deps): Bump library/redis from 7 to 8 in /docker/compose @[dependabot[bot]](https://github.com/apps/dependabot) ([#9879](https://github.com/paperless-ngx/paperless-ngx/pull/9879))
- Chore(deps): Bump astral-sh/setup-uv from 5 to 6 in the actions group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9842](https://github.com/paperless-ngx/paperless-ngx/pull/9842))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 14 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9848](https://github.com/paperless-ngx/paperless-ngx/pull/9848))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9849](https://github.com/paperless-ngx/paperless-ngx/pull/9849))
- Chore(deps-dev): Bump @types/node from 22.13.17 to 22.15.3 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9850](https://github.com/paperless-ngx/paperless-ngx/pull/9850))
- docker(deps): Bump astral-sh/uv from 0.6.14-python3.12-bookworm-slim to 0.6.16-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9767](https://github.com/paperless-ngx/paperless-ngx/pull/9767))
- docker-compose(deps): bump gotenberg/gotenberg from 8.19 to 8.20 in /docker/compose @[dependabot[bot]](https://github.com/apps/dependabot) ([#9661](https://github.com/paperless-ngx/paperless-ngx/pull/9661))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 17 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9768](https://github.com/paperless-ngx/paperless-ngx/pull/9768))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9770](https://github.com/paperless-ngx/paperless-ngx/pull/9770))
- Chore(deps-dev): Bump jest-preset-angular from 14.5.4 to 14.5.5 in /src-ui in the frontend-jest-dependencies group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9769](https://github.com/paperless-ngx/paperless-ngx/pull/9769))
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9764](https://github.com/paperless-ngx/paperless-ngx/pull/9764))
- Chore(deps): Bump the django group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9753](https://github.com/paperless-ngx/paperless-ngx/pull/9753))
- docker(deps): bump astral-sh/uv from 0.6.13-python3.12-bookworm-slim to 0.6.14-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9656](https://github.com/paperless-ngx/paperless-ngx/pull/9656))
</details>
### All App Changes
<details>
<summary>29 changes</summary>
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9921](https://github.com/paperless-ngx/paperless-ngx/pull/9921))
- Fix: handle created change with api version increment, use created only on frontend, deprecate created_date [@shamoon](https://github.com/shamoon) ([#9962](https://github.com/paperless-ngx/paperless-ngx/pull/9962))
- Fixhancement: automatically disable email verification if no smtp setup [@shamoon](https://github.com/shamoon) ([#9949](https://github.com/paperless-ngx/paperless-ngx/pull/9949))
- Fix: ignore logo file from sanity checker [@shamoon](https://github.com/shamoon) ([#9946](https://github.com/paperless-ngx/paperless-ngx/pull/9946))
- [BREAKING] Change: treat created as date not datetime [@shamoon](https://github.com/shamoon) ([#9793](https://github.com/paperless-ngx/paperless-ngx/pull/9793))
- Fix/Chore: replace file drop package [@shamoon](https://github.com/shamoon) ([#9926](https://github.com/paperless-ngx/paperless-ngx/pull/9926))
- Chore: resolve dynamic import warnings from pdfjs, again [@shamoon](https://github.com/shamoon) ([#9924](https://github.com/paperless-ngx/paperless-ngx/pull/9924))
- Enhancement: support negative offset in scheduled workflows [@shamoon](https://github.com/shamoon) ([#9746](https://github.com/paperless-ngx/paperless-ngx/pull/9746))
- Fixhancement: better handle removed social apps in profile [@shamoon](https://github.com/shamoon) ([#9876](https://github.com/paperless-ngx/paperless-ngx/pull/9876))
- Enhancement: add barcode frontend config [@shamoon](https://github.com/shamoon) ([#9742](https://github.com/paperless-ngx/paperless-ngx/pull/9742))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 14 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9848](https://github.com/paperless-ngx/paperless-ngx/pull/9848))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9849](https://github.com/paperless-ngx/paperless-ngx/pull/9849))
- Chore(deps-dev): Bump @types/node from 22.13.17 to 22.15.3 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9850](https://github.com/paperless-ngx/paperless-ngx/pull/9850))
- Fix: correctly handle empty user for old notes api format, fix frontend API version [@shamoon](https://github.com/shamoon) ([#9846](https://github.com/paperless-ngx/paperless-ngx/pull/9846))
- Fix: fix single select in filterable dropdowns when editing [@shamoon](https://github.com/shamoon) ([#9834](https://github.com/paperless-ngx/paperless-ngx/pull/9834))
- Fix: always update classifier task result [@shamoon](https://github.com/shamoon) ([#9817](https://github.com/paperless-ngx/paperless-ngx/pull/9817))
- Fixhancement: check more permissions for status consumer messages [@shamoon](https://github.com/shamoon) ([#9804](https://github.com/paperless-ngx/paperless-ngx/pull/9804))
- Enhancement: support heic images [@shamoon](https://github.com/shamoon) ([#9771](https://github.com/paperless-ngx/paperless-ngx/pull/9771))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 17 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9768](https://github.com/paperless-ngx/paperless-ngx/pull/9768))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9770](https://github.com/paperless-ngx/paperless-ngx/pull/9770))
- Chore(deps-dev): Bump jest-preset-angular from 14.5.4 to 14.5.5 in /src-ui in the frontend-jest-dependencies group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9769](https://github.com/paperless-ngx/paperless-ngx/pull/9769))
- Enhancement: support allauth disable unknown account emails [@shamoon](https://github.com/shamoon) ([#9743](https://github.com/paperless-ngx/paperless-ngx/pull/9743))
- Enhancement: use patch instead of put for frontend document changes [@shamoon](https://github.com/shamoon) ([#9744](https://github.com/paperless-ngx/paperless-ngx/pull/9744))
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9764](https://github.com/paperless-ngx/paperless-ngx/pull/9764))
- Chore(deps): Bump the django group across 1 directory with 6 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9753](https://github.com/paperless-ngx/paperless-ngx/pull/9753))
- Fixhancement: tag plus button should add tag to doc [@shamoon](https://github.com/shamoon) ([#9762](https://github.com/paperless-ngx/paperless-ngx/pull/9762))
- Fix: fix zoom increase/decrease buttons in FF [@shamoon](https://github.com/shamoon) ([#9761](https://github.com/paperless-ngx/paperless-ngx/pull/9761))
- Chore: switch from os.path to pathlib.Path [@gothicVI](https://github.com/gothicVI) ([#9339](https://github.com/paperless-ngx/paperless-ngx/pull/9339))
- Fix: include subpath in drf-spectacular settings if set [@shamoon](https://github.com/shamoon) ([#9738](https://github.com/paperless-ngx/paperless-ngx/pull/9738))
</details>
## paperless-ngx 2.15.3
### Bug Fixes
- Fix: do not try deleting original file that was moved to trash dir [@shamoon](https://github.com/shamoon) ([#9684](https://github.com/paperless-ngx/paperless-ngx/pull/9684))
- Fix: preserve non-ASCII filenames in document downloads [@shamoon](https://github.com/shamoon) ([#9702](https://github.com/paperless-ngx/paperless-ngx/pull/9702))
- Fix: fix breaking api change to document notes user field [@shamoon](https://github.com/shamoon) ([#9714](https://github.com/paperless-ngx/paperless-ngx/pull/9714))
- Fix: another doc link fix [@shamoon](https://github.com/shamoon) ([#9700](https://github.com/paperless-ngx/paperless-ngx/pull/9700))
- Fix: correctly handle dict data with webhook [@shamoon](https://github.com/shamoon) ([#9674](https://github.com/paperless-ngx/paperless-ngx/pull/9674))
### All App Changes
<details>
<summary>5 changes</summary>
- Fix: do not try deleting original file that was moved to trash dir [@shamoon](https://github.com/shamoon) ([#9684](https://github.com/paperless-ngx/paperless-ngx/pull/9684))
- Fix: preserve non-ASCII filenames in document downloads [@shamoon](https://github.com/shamoon) ([#9702](https://github.com/paperless-ngx/paperless-ngx/pull/9702))
- Fix: fix breaking api change to document notes user field [@shamoon](https://github.com/shamoon) ([#9714](https://github.com/paperless-ngx/paperless-ngx/pull/9714))
- Fix: another doc link fix [@shamoon](https://github.com/shamoon) ([#9700](https://github.com/paperless-ngx/paperless-ngx/pull/9700))
- Fix: correctly handle dict data with webhook [@shamoon](https://github.com/shamoon) ([#9674](https://github.com/paperless-ngx/paperless-ngx/pull/9674))
</details>
## paperless-ngx 2.15.2
### Bug Fixes
- Fix: Adds better handling during folder checking/creation/permissions for non-root [@stumpylog](https://github.com/stumpylog) ([#9616](https://github.com/paperless-ngx/paperless-ngx/pull/9616))
- Fix: Explicitly set the HOME environment to resolve issues running as root with database certificates [@stumpylog](https://github.com/stumpylog) ([#9643](https://github.com/paperless-ngx/paperless-ngx/pull/9643))
- Fix: prevent self-linking when bulk edit doc link [@shamoon](https://github.com/shamoon) ([#9629](https://github.com/paperless-ngx/paperless-ngx/pull/9629))
### Dependencies
- Chore: Bump celery to 5.5.1 [@hannesortmeier](https://github.com/hannesortmeier) ([#9642](https://github.com/paperless-ngx/paperless-ngx/pull/9642))
### All App Changes
<details>
<summary>4 changes</summary>
- Tweak: consistently use created date when displaying doc in list [@shamoon](https://github.com/shamoon) ([#9651](https://github.com/paperless-ngx/paperless-ngx/pull/9651))
- Fix: Adds better handling during folder checking/creation/permissions for non-root [@stumpylog](https://github.com/stumpylog) ([#9616](https://github.com/paperless-ngx/paperless-ngx/pull/9616))
- Fix: Explicitly set the HOME environment to resolve issues running as root with database certificates [@stumpylog](https://github.com/stumpylog) ([#9643](https://github.com/paperless-ngx/paperless-ngx/pull/9643))
- Fix: prevent self-linking when bulk edit doc link [@shamoon](https://github.com/shamoon) ([#9629](https://github.com/paperless-ngx/paperless-ngx/pull/9629))
</details>
## paperless-ngx 2.15.1
### Bug Fixes
- Fix: Run migration lock as the correct user [@stumpylog](https://github.com/stumpylog) ([#9604](https://github.com/paperless-ngx/paperless-ngx/pull/9604))
- Fix: Adds a warning to the user if their secret file includes a trailing newline [@stumpylog](https://github.com/stumpylog) ([#9601](https://github.com/paperless-ngx/paperless-ngx/pull/9601))
- Fix: correct download filename in 2.15.0 [@shamoon](https://github.com/shamoon) ([#9599](https://github.com/paperless-ngx/paperless-ngx/pull/9599))
- Fix: dont exclude matching check for scheduled workflows [@shamoon](https://github.com/shamoon) ([#9594](https://github.com/paperless-ngx/paperless-ngx/pull/9594))
### Maintenance
- docker(deps): Bump astral-sh/uv from 0.6.9-python3.12-bookworm-slim to 0.6.13-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9573](https://github.com/paperless-ngx/paperless-ngx/pull/9573))
### Dependencies
- docker(deps): Bump astral-sh/uv from 0.6.9-python3.12-bookworm-slim to 0.6.13-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9573](https://github.com/paperless-ngx/paperless-ngx/pull/9573))
- Chore: move to whoosh-reloaded, for now [@shamoon](https://github.com/shamoon) ([#9605](https://github.com/paperless-ngx/paperless-ngx/pull/9605))
### All App Changes
<details>
<summary>4 changes</summary>
- Fix: Run migration lock as the correct user [@stumpylog](https://github.com/stumpylog) ([#9604](https://github.com/paperless-ngx/paperless-ngx/pull/9604))
- Fix: Adds a warning to the user if their secret file includes a trailing newline [@stumpylog](https://github.com/stumpylog) ([#9601](https://github.com/paperless-ngx/paperless-ngx/pull/9601))
- Fix: correct download filename in 2.15.0 [@shamoon](https://github.com/shamoon) ([#9599](https://github.com/paperless-ngx/paperless-ngx/pull/9599))
- Fix: dont exclude matching check for scheduled workflows [@shamoon](https://github.com/shamoon) ([#9594](https://github.com/paperless-ngx/paperless-ngx/pull/9594))
</details>
## paperless-ngx 2.15.0
### Features
- Enhancement: allow webUI first account signup [@shamoon](https://github.com/shamoon) ([#9500](https://github.com/paperless-ngx/paperless-ngx/pull/9500))
- Enhancement: support more 'not assigned' filtering, refactor [@shamoon](https://github.com/shamoon) ([#9429](https://github.com/paperless-ngx/paperless-ngx/pull/9429))
- Enhancement: reorganize dates dropdown, add more relative options [@shamoon](https://github.com/shamoon) ([#9307](https://github.com/paperless-ngx/paperless-ngx/pull/9307))
- Enhancement: add switch to allow merging non-PDFs with archive version [@shamoon](https://github.com/shamoon) ([#9305](https://github.com/paperless-ngx/paperless-ngx/pull/9305))
- Enhancement: support assigning custom field values in workflows [@shamoon](https://github.com/shamoon) ([#9272](https://github.com/paperless-ngx/paperless-ngx/pull/9272))
- Enhancement: Add slugify filter in templating [@hwaterke](https://github.com/hwaterke) ([#9269](https://github.com/paperless-ngx/paperless-ngx/pull/9269))
- Feature: Switch webserver to granian [@stumpylog](https://github.com/stumpylog) ([#9218](https://github.com/paperless-ngx/paperless-ngx/pull/9218))
- Enhancement: relocate and smaller upload widget, dont limit upload list [@shamoon](https://github.com/shamoon) ([#9244](https://github.com/paperless-ngx/paperless-ngx/pull/9244))
- Enhancement: run tasks from system status, report sanity check, simpler classifier check, styling updates [@shamoon](https://github.com/shamoon) ([#9106](https://github.com/paperless-ngx/paperless-ngx/pull/9106))
- Enhancement: include celery log in logs view [@shamoon](https://github.com/shamoon) ([#9214](https://github.com/paperless-ngx/paperless-ngx/pull/9214))
- Enhancement: support default groups for regular and social account signup, syncing on login [@shamoon](https://github.com/shamoon) ([#9039](https://github.com/paperless-ngx/paperless-ngx/pull/9039))
- Enhancement: allow disabling the filesystem consumer [@shamoon](https://github.com/shamoon) ([#9199](https://github.com/paperless-ngx/paperless-ngx/pull/9199))
- Feature: email document [@shamoon](https://github.com/shamoon) ([#8950](https://github.com/paperless-ngx/paperless-ngx/pull/8950))
- Enhancement: webui workflowtrigger source option [@shamoon](https://github.com/shamoon) ([#9170](https://github.com/paperless-ngx/paperless-ngx/pull/9170))
- Enhancement: use charfield for webhook url, custom validation [@shamoon](https://github.com/shamoon) ([#9128](https://github.com/paperless-ngx/paperless-ngx/pull/9128))
- Feature: Chinese Traditional translation [@LokiHung](https://github.com/LokiHung) ([#9076](https://github.com/paperless-ngx/paperless-ngx/pull/9076))
- Enhancement: Use cached sessions for a minor performance improvement [@stumpylog](https://github.com/stumpylog) ([#9074](https://github.com/paperless-ngx/paperless-ngx/pull/9074))
- Feature: openapi spec, full api browser [@shamoon](https://github.com/shamoon) ([#8948](https://github.com/paperless-ngx/paperless-ngx/pull/8948))
- Enhancement: filter by file type [@shamoon](https://github.com/shamoon) ([#8946](https://github.com/paperless-ngx/paperless-ngx/pull/8946))
- Feature: Transition Docker to use s6 overlay [@stumpylog](https://github.com/stumpylog) ([#8886](https://github.com/paperless-ngx/paperless-ngx/pull/8886))
- Feature: better toast notifications management [@shamoon](https://github.com/shamoon) ([#8980](https://github.com/paperless-ngx/paperless-ngx/pull/8980))
- Enhancement: date picker and date filter dropdown improvements [@shamoon](https://github.com/shamoon) ([#9033](https://github.com/paperless-ngx/paperless-ngx/pull/9033))
- Tweak: more accurate classifier last trained time [@shamoon](https://github.com/shamoon) ([#9004](https://github.com/paperless-ngx/paperless-ngx/pull/9004))
- Enhancement: allow setting default pdf zoom [@shamoon](https://github.com/shamoon) ([#9017](https://github.com/paperless-ngx/paperless-ngx/pull/9017))
### Bug Fixes
- Fix: ensure only matched scheduled workflows are applied [@shamoon](https://github.com/shamoon) ([#9580](https://github.com/paperless-ngx/paperless-ngx/pull/9580))
- Fix: fix large doc thumb hidden at unexpected screen sizes [@shamoon](https://github.com/shamoon) ([#9559](https://github.com/paperless-ngx/paperless-ngx/pull/9559))
- Fix: fix potential race condition when creating new cf from doc details [@shamoon](https://github.com/shamoon) ([#9542](https://github.com/paperless-ngx/paperless-ngx/pull/9542))
- Fix: fix doc link input [@shamoon](https://github.com/shamoon) ([#9533](https://github.com/paperless-ngx/paperless-ngx/pull/9533))
- Fix: only overwrite existing cf values in workflow if set [@shamoon](https://github.com/shamoon) ([#9459](https://github.com/paperless-ngx/paperless-ngx/pull/9459))
- Fix: fix auto-close when doc update no longer has permissions [@shamoon](https://github.com/shamoon) ([#9453](https://github.com/paperless-ngx/paperless-ngx/pull/9453))
- Change: better handle permissions in patch requests [@shamoon](https://github.com/shamoon) ([#9393](https://github.com/paperless-ngx/paperless-ngx/pull/9393))
- Fix: use correct filename with webhook [@shamoon](https://github.com/shamoon) ([#9392](https://github.com/paperless-ngx/paperless-ngx/pull/9392))
- Change: sync OIDC groups on first login too [@shamoon](https://github.com/shamoon) ([#9387](https://github.com/paperless-ngx/paperless-ngx/pull/9387))
- Fix: only parse custom field queries when valid [@shamoon](https://github.com/shamoon) ([#9384](https://github.com/paperless-ngx/paperless-ngx/pull/9384))
- Fix: Allow setting of other Granian options [@stumpylog](https://github.com/stumpylog) ([#9360](https://github.com/paperless-ngx/paperless-ngx/pull/9360))
- Fix: Always clean up INotify [@stumpylog](https://github.com/stumpylog) ([#9359](https://github.com/paperless-ngx/paperless-ngx/pull/9359))
- Fix typo in inactive account template [@ocean90](https://github.com/ocean90) ([#9356](https://github.com/paperless-ngx/paperless-ngx/pull/9356))
- Fix: fix notes serializing in API document response [@shamoon](https://github.com/shamoon) ([#9336](https://github.com/paperless-ngx/paperless-ngx/pull/9336))
- Fix: correct all results with whoosh queries [@shamoon](https://github.com/shamoon) ([#9331](https://github.com/paperless-ngx/paperless-ngx/pull/9331))
- Fix: fix typo in altered migration [@gothicVI](https://github.com/gothicVI) ([#9321](https://github.com/paperless-ngx/paperless-ngx/pull/9321))
- Fix: add account_inactive template / url [@shamoon](https://github.com/shamoon) ([#9322](https://github.com/paperless-ngx/paperless-ngx/pull/9322))
- Fix: Switches data to content to upload raw bytes/text content [@stumpylog](https://github.com/stumpylog) ([#9293](https://github.com/paperless-ngx/paperless-ngx/pull/9293))
- Fix: handle null workflow body and email subject [@shamoon](https://github.com/shamoon) ([#9271](https://github.com/paperless-ngx/paperless-ngx/pull/9271))
- Fix: cleanup saved view references on custom field deletion, auto-refresh views, show error on saved view save [@shamoon](https://github.com/shamoon) ([#9225](https://github.com/paperless-ngx/paperless-ngx/pull/9225))
- Fix: revert thumbnail CSS workaround in favor of GPU workaround [@shamoon](https://github.com/shamoon) ([#9219](https://github.com/paperless-ngx/paperless-ngx/pull/9219))
- Fix: correct split confirm removal [@shamoon](https://github.com/shamoon) ([#9195](https://github.com/paperless-ngx/paperless-ngx/pull/9195))
- Fix: saved views do not return to default display fields after setting and then removing [@shamoon](https://github.com/shamoon) ([#9168](https://github.com/paperless-ngx/paperless-ngx/pull/9168))
- Fix: correct logged number of deleted documents on trash empty [@shamoon](https://github.com/shamoon) ([#9148](https://github.com/paperless-ngx/paperless-ngx/pull/9148))
- Fix: include account confirm email allauth URL [@shamoon](https://github.com/shamoon) ([#9147](https://github.com/paperless-ngx/paperless-ngx/pull/9147))
- Fix: remove additional scrollbar from popup preview [@shamoon](https://github.com/shamoon) ([#9140](https://github.com/paperless-ngx/paperless-ngx/pull/9140))
- Fix: wrap selected display fields [@shamoon](https://github.com/shamoon) ([#9139](https://github.com/paperless-ngx/paperless-ngx/pull/9139))
- Fix: reset documents sort field if user deletes the custom field [@shamoon](https://github.com/shamoon) ([#9127](https://github.com/paperless-ngx/paperless-ngx/pull/9127))
- Fix: limit document title length in workflows [@shamoon](https://github.com/shamoon) ([#9085](https://github.com/paperless-ngx/paperless-ngx/pull/9085))
- Fix: include doc link input import in custom fields query dropdown [@shamoon](https://github.com/shamoon) ([#9058](https://github.com/paperless-ngx/paperless-ngx/pull/9058))
- Fix: deselect and trigger refresh for deleted documents from bulk operations with delete originals [@shamoon](https://github.com/shamoon) ([#8996](https://github.com/paperless-ngx/paperless-ngx/pull/8996))
- Fix: allow empty email in profile [@shamoon](https://github.com/shamoon) ([#9012](https://github.com/paperless-ngx/paperless-ngx/pull/9012))
### Maintenance
- docker(deps): Bump astral-sh/uv from 0.6.5-python3.12-bookworm-slim to 0.6.9-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9488](https://github.com/paperless-ngx/paperless-ngx/pull/9488))
- Chore: Enables dependabot for Dockerfile and our Compose files [@stumpylog](https://github.com/stumpylog) ([#9342](https://github.com/paperless-ngx/paperless-ngx/pull/9342))
- Chore: ensure codecov upload gets attempted [@shamoon](https://github.com/shamoon) ([#9308](https://github.com/paperless-ngx/paperless-ngx/pull/9308))
- Chore: Split out some items into extras [@stumpylog](https://github.com/stumpylog) ([#9297](https://github.com/paperless-ngx/paperless-ngx/pull/9297))
- Chore: Enables Codecov test reporting for the backend [@stumpylog](https://github.com/stumpylog) ([#9295](https://github.com/paperless-ngx/paperless-ngx/pull/9295))
- Chore: Combine Python settings files [@stumpylog](https://github.com/stumpylog) ([#9292](https://github.com/paperless-ngx/paperless-ngx/pull/9292))
### Dependencies
<details>
<summary>43 changes</summary>
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 20 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9536](https://github.com/paperless-ngx/paperless-ngx/pull/9536))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9538](https://github.com/paperless-ngx/paperless-ngx/pull/9538))
- Chore(deps-dev): Bump @types/node from 22.13.9 to 22.13.17 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9539](https://github.com/paperless-ngx/paperless-ngx/pull/9539))
- Chore(deps-dev): Bump jest-preset-angular from 14.5.3 to 14.5.4 in /src-ui in the frontend-jest-dependencies group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9537](https://github.com/paperless-ngx/paperless-ngx/pull/9537))
- Chore(deps-dev): Bump @playwright/test from 1.50.1 to 1.51.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9540](https://github.com/paperless-ngx/paperless-ngx/pull/9540))
- Chore(deps): Bump django from 5.1.6 to 5.1.7 in the django group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9486](https://github.com/paperless-ngx/paperless-ngx/pull/9486))
- docker(deps): Bump astral-sh/uv from 0.6.5-python3.12-bookworm-slim to 0.6.9-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9488](https://github.com/paperless-ngx/paperless-ngx/pull/9488))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9372](https://github.com/paperless-ngx/paperless-ngx/pull/9372))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 20 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9371](https://github.com/paperless-ngx/paperless-ngx/pull/9371))
- Chore(deps): Update ocrmypdf requirement from ~=16.9.0 to ~=16.10.0 @[dependabot[bot]](https://github.com/apps/dependabot) ([#9348](https://github.com/paperless-ngx/paperless-ngx/pull/9348))
- Chore(deps): Update drf-spectacular-sidecar requirement from ~=2025.2.1 to ~=2025.3.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#9347](https://github.com/paperless-ngx/paperless-ngx/pull/9347))
- Chore(deps): Bump the small-changes group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9345](https://github.com/paperless-ngx/paperless-ngx/pull/9345))
- docker-compose(deps): Bump library/postgres from 16 to 17 in /docker/compose @[dependabot[bot]](https://github.com/apps/dependabot) ([#9353](https://github.com/paperless-ngx/paperless-ngx/pull/9353))
- docker(deps): Bump astral-sh/uv from 0.6.3-python3.12-bookworm-slim to 0.6.5-python3.12-bookworm-slim @[dependabot[bot]](https://github.com/apps/dependabot) ([#9344](https://github.com/paperless-ngx/paperless-ngx/pull/9344))
- Chore(deps-dev): Bump the frontend-angular-dependencies group in /src-ui with 5 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9288](https://github.com/paperless-ngx/paperless-ngx/pull/9288))
- Chore(deps-dev): Bump @types/node from 22.13.8 to 22.13.9 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9290](https://github.com/paperless-ngx/paperless-ngx/pull/9290))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9289](https://github.com/paperless-ngx/paperless-ngx/pull/9289))
- Chore(deps-dev): Bump @types/node from 22.13.5 to 22.13.8 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9267](https://github.com/paperless-ngx/paperless-ngx/pull/9267))
- Chore(deps): Bump stumpylog/image-cleaner-action from 0.9.0 to 0.10.0 in the actions group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9252](https://github.com/paperless-ngx/paperless-ngx/pull/9252))
- Chore(deps-dev): Bump the development group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9253](https://github.com/paperless-ngx/paperless-ngx/pull/9253))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.8.0 to 1.9.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9260](https://github.com/paperless-ngx/paperless-ngx/pull/9260))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9256](https://github.com/paperless-ngx/paperless-ngx/pull/9256))
- Chore(deps): Bump uuid from 11.0.5 to 11.1.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9259](https://github.com/paperless-ngx/paperless-ngx/pull/9259))
- Chore(deps-dev): Bump jest-preset-angular from 14.5.1 to 14.5.3 in /src-ui in the frontend-jest-dependencies group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9255](https://github.com/paperless-ngx/paperless-ngx/pull/9255))
- Chore(deps): Bump rxjs from 7.8.1 to 7.8.2 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9258](https://github.com/paperless-ngx/paperless-ngx/pull/9258))
- Chore(deps-dev): Bump @types/node from 22.13.0 to 22.13.5 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9257](https://github.com/paperless-ngx/paperless-ngx/pull/9257))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 22 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9254](https://github.com/paperless-ngx/paperless-ngx/pull/9254))
- Chore(deps): Bump django-filter from 24.3 to 25.1 in the django group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9143](https://github.com/paperless-ngx/paperless-ngx/pull/9143))
- Chore(deps-dev): Bump mkdocs-material from 9.6.3 to 9.6.4 in the development group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9142](https://github.com/paperless-ngx/paperless-ngx/pull/9142))
- Dependencies: Updates to jbig2enc 0.30 [@stumpylog](https://github.com/stumpylog) ([#9092](https://github.com/paperless-ngx/paperless-ngx/pull/9092))
- Chore(deps): Bump cryptography from 44.0.0 to 44.0.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#9080](https://github.com/paperless-ngx/paperless-ngx/pull/9080))
- Chore(deps): Bump the small-changes group with 7 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9064](https://github.com/paperless-ngx/paperless-ngx/pull/9064))
- Chore(deps-dev): Bump the development group with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9061](https://github.com/paperless-ngx/paperless-ngx/pull/9061))
- Chore(deps): Bump the django group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9065](https://github.com/paperless-ngx/paperless-ngx/pull/9065))
- Chore(deps): Bump drf-spectacular-sidecar from 2024.11.1 to 2025.2.1 in the major-versions group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9063](https://github.com/paperless-ngx/paperless-ngx/pull/9063))
- Chore(deps-dev): Bump the development group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9013](https://github.com/paperless-ngx/paperless-ngx/pull/9013))
- Chore(deps): Bump django-soft-delete from 1.0.16 to 1.0.18 in the django group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9014](https://github.com/paperless-ngx/paperless-ngx/pull/9014))
- Chore(deps): Bump uuid from 11.0.2 to 11.0.5 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8992](https://github.com/paperless-ngx/paperless-ngx/pull/8992))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.2.1 to 1.8.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8991](https://github.com/paperless-ngx/paperless-ngx/pull/8991))
- Chore(deps-dev): Bump @playwright/test from 1.48.2 to 1.50.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8993](https://github.com/paperless-ngx/paperless-ngx/pull/8993))
- Chore(deps-dev): Bump @types/node from 22.8.6 to 22.13.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8989](https://github.com/paperless-ngx/paperless-ngx/pull/8989))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#8988](https://github.com/paperless-ngx/paperless-ngx/pull/8988))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 23 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#8986](https://github.com/paperless-ngx/paperless-ngx/pull/8986))
</details>
### All App Changes
<details>
<summary>109 changes</summary>
- Fix: ensure only matched scheduled workflows are applied [@shamoon](https://github.com/shamoon) ([#9580](https://github.com/paperless-ngx/paperless-ngx/pull/9580))
- Fix: fix large doc thumb hidden at unexpected screen sizes [@shamoon](https://github.com/shamoon) ([#9559](https://github.com/paperless-ngx/paperless-ngx/pull/9559))
- Fix: fix potential race condition when creating new cf from doc details [@shamoon](https://github.com/shamoon) ([#9542](https://github.com/paperless-ngx/paperless-ngx/pull/9542))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 20 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9536](https://github.com/paperless-ngx/paperless-ngx/pull/9536))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9538](https://github.com/paperless-ngx/paperless-ngx/pull/9538))
- Chore(deps-dev): Bump @types/node from 22.13.9 to 22.13.17 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9539](https://github.com/paperless-ngx/paperless-ngx/pull/9539))
- Chore(deps-dev): Bump jest-preset-angular from 14.5.3 to 14.5.4 in /src-ui in the frontend-jest-dependencies group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9537](https://github.com/paperless-ngx/paperless-ngx/pull/9537))
- Chore(deps-dev): Bump @playwright/test from 1.50.1 to 1.51.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9540](https://github.com/paperless-ngx/paperless-ngx/pull/9540))
- Fix: fix doc link input [@shamoon](https://github.com/shamoon) ([#9533](https://github.com/paperless-ngx/paperless-ngx/pull/9533))
- Enhancement: allow webUI first account signup [@shamoon](https://github.com/shamoon) ([#9500](https://github.com/paperless-ngx/paperless-ngx/pull/9500))
- Fix: fix cf dropdown placement on mobile [@shamoon](https://github.com/shamoon) ([#9508](https://github.com/paperless-ngx/paperless-ngx/pull/9508))
- Chore(deps): Bump django from 5.1.6 to 5.1.7 in the django group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9486](https://github.com/paperless-ngx/paperless-ngx/pull/9486))
- Fix: only overwrite existing cf values in workflow if set [@shamoon](https://github.com/shamoon) ([#9459](https://github.com/paperless-ngx/paperless-ngx/pull/9459))
- Fix: fix auto-close when doc update no longer has permissions [@shamoon](https://github.com/shamoon) ([#9453](https://github.com/paperless-ngx/paperless-ngx/pull/9453))
- Enhancement: support more 'not assigned' filtering, refactor [@shamoon](https://github.com/shamoon) ([#9429](https://github.com/paperless-ngx/paperless-ngx/pull/9429))
- Change: better handle permissions in patch requests [@shamoon](https://github.com/shamoon) ([#9393](https://github.com/paperless-ngx/paperless-ngx/pull/9393))
- Fix: use correct filename with webhook [@shamoon](https://github.com/shamoon) ([#9392](https://github.com/paperless-ngx/paperless-ngx/pull/9392))
- Change: sync OIDC groups on first login too [@shamoon](https://github.com/shamoon) ([#9387](https://github.com/paperless-ngx/paperless-ngx/pull/9387))
- Fix: only parse custom field queries when valid [@shamoon](https://github.com/shamoon) ([#9384](https://github.com/paperless-ngx/paperless-ngx/pull/9384))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9372](https://github.com/paperless-ngx/paperless-ngx/pull/9372))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 20 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9371](https://github.com/paperless-ngx/paperless-ngx/pull/9371))
- Development: change frontend package manager to pnpm [@shamoon](https://github.com/shamoon) ([#9363](https://github.com/paperless-ngx/paperless-ngx/pull/9363))
- Fix: Allow setting of other Granian options [@stumpylog](https://github.com/stumpylog) ([#9360](https://github.com/paperless-ngx/paperless-ngx/pull/9360))
- Fix: Always clean up INotify [@stumpylog](https://github.com/stumpylog) ([#9359](https://github.com/paperless-ngx/paperless-ngx/pull/9359))
- Tweak: add saved views hint to dashboard [@shamoon](https://github.com/shamoon) ([#9362](https://github.com/paperless-ngx/paperless-ngx/pull/9362))
- Chore(deps): Update ocrmypdf requirement from ~=16.9.0 to ~=16.10.0 @[dependabot[bot]](https://github.com/apps/dependabot) ([#9348](https://github.com/paperless-ngx/paperless-ngx/pull/9348))
- Chore(deps): Update drf-spectacular-sidecar requirement from ~=2025.2.1 to ~=2025.3.1 @[dependabot[bot]](https://github.com/apps/dependabot) ([#9347](https://github.com/paperless-ngx/paperless-ngx/pull/9347))
- Chore(deps): Bump the small-changes group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9345](https://github.com/paperless-ngx/paperless-ngx/pull/9345))
- Ensure the directories have been overridden and created for this test [@stumpylog](https://github.com/stumpylog) ([#9354](https://github.com/paperless-ngx/paperless-ngx/pull/9354))
- Fix typo in inactive account template [@ocean90](https://github.com/ocean90) ([#9356](https://github.com/paperless-ngx/paperless-ngx/pull/9356))
- Fix: fix notes serializing in API document response [@shamoon](https://github.com/shamoon) ([#9336](https://github.com/paperless-ngx/paperless-ngx/pull/9336))
- Fix: correct all results with whoosh queries [@shamoon](https://github.com/shamoon) ([#9331](https://github.com/paperless-ngx/paperless-ngx/pull/9331))
- Fix: fix typo in altered migration [@gothicVI](https://github.com/gothicVI) ([#9321](https://github.com/paperless-ngx/paperless-ngx/pull/9321))
- Fix: add account_inactive template / url [@shamoon](https://github.com/shamoon) ([#9322](https://github.com/paperless-ngx/paperless-ngx/pull/9322))
- Chore: Switch from os.path to pathlib.Path [@gothicVI](https://github.com/gothicVI) ([#9060](https://github.com/paperless-ngx/paperless-ngx/pull/9060))
- Enhancement: reorganize dates dropdown, add more relative options [@shamoon](https://github.com/shamoon) ([#9307](https://github.com/paperless-ngx/paperless-ngx/pull/9307))
- Chore: remove popper preventOverflow fix [@shamoon](https://github.com/shamoon) ([#9306](https://github.com/paperless-ngx/paperless-ngx/pull/9306))
- Enhancement: add switch to allow merging non-PDFs with archive version [@shamoon](https://github.com/shamoon) ([#9305](https://github.com/paperless-ngx/paperless-ngx/pull/9305))
- Enhancement: support assigning custom field values in workflows [@shamoon](https://github.com/shamoon) ([#9272](https://github.com/paperless-ngx/paperless-ngx/pull/9272))
- Chore: add codecov frontend test results [@shamoon](https://github.com/shamoon) ([#9296](https://github.com/paperless-ngx/paperless-ngx/pull/9296))
- Chore: Removes undocumented FileInfo [@stumpylog](https://github.com/stumpylog) ([#9298](https://github.com/paperless-ngx/paperless-ngx/pull/9298))
- Chore(deps-dev): Bump the frontend-angular-dependencies group in /src-ui with 5 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9288](https://github.com/paperless-ngx/paperless-ngx/pull/9288))
- Fix: Switches data to content to upload raw bytes/text content [@stumpylog](https://github.com/stumpylog) ([#9293](https://github.com/paperless-ngx/paperless-ngx/pull/9293))
- Chore: Removes the unused Log model and LogFilterSet [@stumpylog](https://github.com/stumpylog) ([#9294](https://github.com/paperless-ngx/paperless-ngx/pull/9294))
- Chore: Combine Python settings files [@stumpylog](https://github.com/stumpylog) ([#9292](https://github.com/paperless-ngx/paperless-ngx/pull/9292))
- Chore(deps-dev): Bump @types/node from 22.13.8 to 22.13.9 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9290](https://github.com/paperless-ngx/paperless-ngx/pull/9290))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9289](https://github.com/paperless-ngx/paperless-ngx/pull/9289))
- Chore: Switch from pipenv to uv [@stumpylog](https://github.com/stumpylog) ([#9251](https://github.com/paperless-ngx/paperless-ngx/pull/9251))
- Enhancement: Add slugify filter in templating [@hwaterke](https://github.com/hwaterke) ([#9269](https://github.com/paperless-ngx/paperless-ngx/pull/9269))
- Fix: handle null workflow body and email subject [@shamoon](https://github.com/shamoon) ([#9271](https://github.com/paperless-ngx/paperless-ngx/pull/9271))
- Chore(deps-dev): Bump @types/node from 22.13.5 to 22.13.8 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9267](https://github.com/paperless-ngx/paperless-ngx/pull/9267))
- Chore(deps-dev): Bump the development group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9253](https://github.com/paperless-ngx/paperless-ngx/pull/9253))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.8.0 to 1.9.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9260](https://github.com/paperless-ngx/paperless-ngx/pull/9260))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9256](https://github.com/paperless-ngx/paperless-ngx/pull/9256))
- Chore(deps): Bump uuid from 11.0.5 to 11.1.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9259](https://github.com/paperless-ngx/paperless-ngx/pull/9259))
- Chore(deps-dev): Bump jest-preset-angular from 14.5.1 to 14.5.3 in /src-ui in the frontend-jest-dependencies group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9255](https://github.com/paperless-ngx/paperless-ngx/pull/9255))
- Chore(deps): Bump rxjs from 7.8.1 to 7.8.2 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9258](https://github.com/paperless-ngx/paperless-ngx/pull/9258))
- Chore(deps-dev): Bump @types/node from 22.13.0 to 22.13.5 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#9257](https://github.com/paperless-ngx/paperless-ngx/pull/9257))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 22 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9254](https://github.com/paperless-ngx/paperless-ngx/pull/9254))
- Feature: Switch webserver to granian [@stumpylog](https://github.com/stumpylog) ([#9218](https://github.com/paperless-ngx/paperless-ngx/pull/9218))
- Enhancement: relocate and smaller upload widget, dont limit upload list [@shamoon](https://github.com/shamoon) ([#9244](https://github.com/paperless-ngx/paperless-ngx/pull/9244))
- Enhancement: run tasks from system status, report sanity check, simpler classifier check, styling updates [@shamoon](https://github.com/shamoon) ([#9106](https://github.com/paperless-ngx/paperless-ngx/pull/9106))
- Chore: Switch remote version check to HTTPx [@stumpylog](https://github.com/stumpylog) ([#9232](https://github.com/paperless-ngx/paperless-ngx/pull/9232))
- Fix: cleanup saved view references on custom field deletion, auto-refresh views, show error on saved view save [@shamoon](https://github.com/shamoon) ([#9225](https://github.com/paperless-ngx/paperless-ngx/pull/9225))
- Fix: revert thumbnail CSS workaround in favor of GPU workaround [@shamoon](https://github.com/shamoon) ([#9219](https://github.com/paperless-ngx/paperless-ngx/pull/9219))
- Chore: Reduce imports for a slight memory improvement [@stumpylog](https://github.com/stumpylog) ([#9217](https://github.com/paperless-ngx/paperless-ngx/pull/9217))
- Enhancement: include celery log in logs view [@shamoon](https://github.com/shamoon) ([#9214](https://github.com/paperless-ngx/paperless-ngx/pull/9214))
- Enhancement: support default groups for regular and social account signup, syncing on login [@shamoon](https://github.com/shamoon) ([#9039](https://github.com/paperless-ngx/paperless-ngx/pull/9039))
- Enhancement: allow disabling the filesystem consumer [@shamoon](https://github.com/shamoon) ([#9199](https://github.com/paperless-ngx/paperless-ngx/pull/9199))
- Fix: correct split confirm removal [@shamoon](https://github.com/shamoon) ([#9195](https://github.com/paperless-ngx/paperless-ngx/pull/9195))
- Feature: email document [@shamoon](https://github.com/shamoon) ([#8950](https://github.com/paperless-ngx/paperless-ngx/pull/8950))
- Enhancement: webui workflowtrigger source option [@shamoon](https://github.com/shamoon) ([#9170](https://github.com/paperless-ngx/paperless-ngx/pull/9170))
- Fix: saved views do not return to default display fields after setting and then removing [@shamoon](https://github.com/shamoon) ([#9168](https://github.com/paperless-ngx/paperless-ngx/pull/9168))
- Chore(deps): Bump django-filter from 24.3 to 25.1 in the django group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9143](https://github.com/paperless-ngx/paperless-ngx/pull/9143))
- Chore(deps-dev): Bump mkdocs-material from 9.6.3 to 9.6.4 in the development group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9142](https://github.com/paperless-ngx/paperless-ngx/pull/9142))
- Fix: correct logged number of deleted documents on trash empty [@shamoon](https://github.com/shamoon) ([#9148](https://github.com/paperless-ngx/paperless-ngx/pull/9148))
- Fix: include account confirm email allauth URL [@shamoon](https://github.com/shamoon) ([#9147](https://github.com/paperless-ngx/paperless-ngx/pull/9147))
- Fix: remove additional scrollbar from popup preview [@shamoon](https://github.com/shamoon) ([#9140](https://github.com/paperless-ngx/paperless-ngx/pull/9140))
- Fix: wrap selected display fields [@shamoon](https://github.com/shamoon) ([#9139](https://github.com/paperless-ngx/paperless-ngx/pull/9139))
- Enhancement: use charfield for webhook url, custom validation [@shamoon](https://github.com/shamoon) ([#9128](https://github.com/paperless-ngx/paperless-ngx/pull/9128))
- Fix: reset documents sort field if user deletes the custom field [@shamoon](https://github.com/shamoon) ([#9127](https://github.com/paperless-ngx/paperless-ngx/pull/9127))
- Chore: more efficient select cf update handler [@shamoon](https://github.com/shamoon) ([#9099](https://github.com/paperless-ngx/paperless-ngx/pull/9099))
- Fix: limit document title length in workflows [@shamoon](https://github.com/shamoon) ([#9085](https://github.com/paperless-ngx/paperless-ngx/pull/9085))
- Feature: Chinese Traditional translation [@LokiHung](https://github.com/LokiHung) ([#9076](https://github.com/paperless-ngx/paperless-ngx/pull/9076))
- Enhancement: Use cached sessions for a minor performance improvement [@stumpylog](https://github.com/stumpylog) ([#9074](https://github.com/paperless-ngx/paperless-ngx/pull/9074))
- Chore(deps): Bump the small-changes group with 7 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9064](https://github.com/paperless-ngx/paperless-ngx/pull/9064))
- Chore(deps-dev): Bump the development group with 3 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9061](https://github.com/paperless-ngx/paperless-ngx/pull/9061))
- Chore(deps): Bump the django group across 1 directory with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9065](https://github.com/paperless-ngx/paperless-ngx/pull/9065))
- Chore(deps): Bump drf-spectacular-sidecar from 2024.11.1 to 2025.2.1 in the major-versions group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9063](https://github.com/paperless-ngx/paperless-ngx/pull/9063))
- Feature: openapi spec, full api browser [@shamoon](https://github.com/shamoon) ([#8948](https://github.com/paperless-ngx/paperless-ngx/pull/8948))
- Fix: include doc link input import in custom fields query dropdown [@shamoon](https://github.com/shamoon) ([#9058](https://github.com/paperless-ngx/paperless-ngx/pull/9058))
- Enhancement: filter by file type [@shamoon](https://github.com/shamoon) ([#8946](https://github.com/paperless-ngx/paperless-ngx/pull/8946))
- Enhancement: add layout options for email conversion [@RazielleS](https://github.com/RazielleS) ([#8907](https://github.com/paperless-ngx/paperless-ngx/pull/8907))
- Chore: Enable ruff FBT [@gothicVI](https://github.com/gothicVI) ([#8645](https://github.com/paperless-ngx/paperless-ngx/pull/8645))
- Feature: better toast notifications management [@shamoon](https://github.com/shamoon) ([#8980](https://github.com/paperless-ngx/paperless-ngx/pull/8980))
- Enhancement: date picker and date filter dropdown improvements [@shamoon](https://github.com/shamoon) ([#9033](https://github.com/paperless-ngx/paperless-ngx/pull/9033))
- Fix: deselect and trigger refresh for deleted documents from bulk operations with delete originals [@shamoon](https://github.com/shamoon) ([#8996](https://github.com/paperless-ngx/paperless-ngx/pull/8996))
- Tweak: improve date matching regex for dates after numbers [@XstreamGit](https://github.com/XstreamGit) ([#8964](https://github.com/paperless-ngx/paperless-ngx/pull/8964))
- Tweak: more accurate classifier last trained time [@shamoon](https://github.com/shamoon) ([#9004](https://github.com/paperless-ngx/paperless-ngx/pull/9004))
- Enhancement: allow setting default pdf zoom [@shamoon](https://github.com/shamoon) ([#9017](https://github.com/paperless-ngx/paperless-ngx/pull/9017))
- Chore(deps-dev): Bump the development group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#9013](https://github.com/paperless-ngx/paperless-ngx/pull/9013))
- Chore(deps): Bump django-soft-delete from 1.0.16 to 1.0.18 in the django group @[dependabot[bot]](https://github.com/apps/dependabot) ([#9014](https://github.com/paperless-ngx/paperless-ngx/pull/9014))
- Fix: allow empty email in profile [@shamoon](https://github.com/shamoon) ([#9012](https://github.com/paperless-ngx/paperless-ngx/pull/9012))
- Chore(deps): Bump uuid from 11.0.2 to 11.0.5 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8992](https://github.com/paperless-ngx/paperless-ngx/pull/8992))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.2.1 to 1.8.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8991](https://github.com/paperless-ngx/paperless-ngx/pull/8991))
- Chore(deps-dev): Bump @playwright/test from 1.48.2 to 1.50.1 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8993](https://github.com/paperless-ngx/paperless-ngx/pull/8993))
- Chore(deps-dev): Bump @types/node from 22.8.6 to 22.13.0 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8989](https://github.com/paperless-ngx/paperless-ngx/pull/8989))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#8988](https://github.com/paperless-ngx/paperless-ngx/pull/8988))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 23 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#8986](https://github.com/paperless-ngx/paperless-ngx/pull/8986))
</details>
## paperless-ngx 2.14.7 ## paperless-ngx 2.14.7
### Features ### Features
@@ -5449,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.
@@ -404,7 +406,7 @@ set this value to /paperless. No trailing slash!
#### [`PAPERLESS_STATIC_URL=<path>`](#PAPERLESS_STATIC_URL) {#PAPERLESS_STATIC_URL} #### [`PAPERLESS_STATIC_URL=<path>`](#PAPERLESS_STATIC_URL) {#PAPERLESS_STATIC_URL}
: Override the STATIC_URL here. Unless you're hosting Paperless off a : Override the STATIC_URL here. Unless you're hosting Paperless off a
subdomain like /paperless/, you probably don't need to change this. specific path like /paperless/, you probably don't need to change this.
If you do change it, be sure to include the trailing slash. If you do change it, be sure to include the trailing slash.
Defaults to "/static/". Defaults to "/static/".
@@ -629,7 +631,13 @@ If both the [PAPERLESS_ACCOUNT_DEFAULT_GROUPS](#PAPERLESS_ACCOUNT_DEFAULT_GROUPS
!!! note !!! note
If you do not have a working email server set up you should set this to 'none'. If you do not have a working email server set up this will be set to 'none'.
#### [`PAPERLESS_ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS=<bool>`](#PAPERLESS_ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS) {#PAPERLESS_ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS}
: See the relevant [django-allauth documentation](https://docs.allauth.org/en/latest/account/configuration.html)
Defaults to True (from allauth)
#### [`PAPERLESS_DISABLE_REGULAR_LOGIN=<bool>`](#PAPERLESS_DISABLE_REGULAR_LOGIN) {#PAPERLESS_DISABLE_REGULAR_LOGIN} #### [`PAPERLESS_DISABLE_REGULAR_LOGIN=<bool>`](#PAPERLESS_DISABLE_REGULAR_LOGIN) {#PAPERLESS_DISABLE_REGULAR_LOGIN}
@@ -1057,9 +1065,9 @@ be used with caution!
## Document Consumption {#consume_config} ## Document Consumption {#consume_config}
#### [`PAPERLESS_CONSUMER_DISABLE=<bool>`](#PAPERLESS_CONSUMER_DISABLE) {#PAPERLESS_CONSUMER_DISABLE} #### [`PAPERLESS_CONSUMER_DISABLE`](#PAPERLESS_CONSUMER_DISABLE) {#PAPERLESS_CONSUMER_DISABLE}
: Completely disable the directory-based consumer in docker. If you don't plan to consume documents : If set (to anything), this completely disables the directory-based consumer in docker. If you don't plan to consume documents
via the consumption directory, you can disable the consumer to save resources. via the consumption directory, you can disable the consumer to save resources.
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES} #### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
@@ -1670,7 +1678,7 @@ started by the container.
## Email sending ## Email sending
Setting an SMTP server for the backend will allow you to reset your Setting an SMTP server for the backend will allow you to use the Email workflow action, send documents from the UI as well as reset your
password. All of these options come from their similarly-named [Django settings](https://docs.djangoproject.com/en/4.2/ref/settings/#email-host) password. All of these options come from their similarly-named [Django settings](https://docs.djangoproject.com/en/4.2/ref/settings/#email-host)
#### [`PAPERLESS_EMAIL_HOST=<str>`](#PAPERLESS_EMAIL_HOST) {#PAPERLESS_EMAIL_HOST} #### [`PAPERLESS_EMAIL_HOST=<str>`](#PAPERLESS_EMAIL_HOST) {#PAPERLESS_EMAIL_HOST}

View File

@@ -84,7 +84,7 @@ first-time setup.
$ uv run pre-commit install $ uv run pre-commit install
``` ```
6. Apply migrations and create a superuser for your development instance: 6. Apply migrations and create a superuser (also can be done via the web UI) for your development instance:
```bash ```bash
# src/ # src/

View File

@@ -112,30 +112,6 @@ able to run paperless, you're a bit on your own. If you can't run the
docker image, the documentation has instructions for bare metal docker image, the documentation has instructions for bare metal
installs. installs.
## _How do I proxy this with NGINX?_
**A:** See [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx#nginx).
## _How do I get WebSocket support with Apache mod_wsgi_?
**A:** `mod_wsgi` by itself does not support ASGI. Paperless will
continue to work with WSGI, but certain features such as status
notifications about document consumption won't be available.
If you want to continue using `mod_wsgi`, you will have to run an
ASGI-enabled web server as well that processes WebSocket connections,
and configure Apache to redirect WebSocket connections to this server.
Multiple options for ASGI servers exist:
- `gunicorn` with `uvicorn` as the worker implementation (the default
of paperless)
- `daphne` as a standalone server, which is the reference
implementation for ASGI.
- `uvicorn` as a standalone server
You may also find the [Django documentation](https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/) on ASGI
useful to review.
## _What about the Redis licensing change and using one of the open source forks_? ## _What about the Redis licensing change and using one of the open source forks_?
Currently (October 2024), forks of Redis such as Valkey or Redirect are not officially supported by our upstream Currently (October 2024), forks of Redis such as Valkey or Redirect are not officially supported by our upstream

View File

@@ -197,7 +197,7 @@ People interested in continuing the work on paperless-ngx are encouraged to reac
### Translation ### Translation
Paperless-ngx is available in many languages that are coordinated on [Crowdin](https://crwd.in/paperless-ngx). If you want to help out by translating paperless-ngx into your language, please head over to the [Paperless-ngx project at Crowdin](https://crwd.in/paperless-ngx), and thank you! Paperless-ngx is available in many languages that are coordinated on [Crowdin](https://crowdin.com/project/paperless-ngx). If you want to help out by translating paperless-ngx into your language, please head over to the [Paperless-ngx project at Crowdin](https://crowdin.com/project/paperless-ngx), and thank you!
## Scanners & Software ## Scanners & Software

View File

@@ -131,26 +131,11 @@ account. The script essentially automatically performs the steps described in [D
by default but you can change the image to pull from Docker Hub by changing the `image` by default but you can change the image to pull from Docker Hub by changing the `image`
line to `image: paperlessngx/paperless-ngx:latest`. line to `image: paperlessngx/paperless-ngx:latest`.
6. To be able to login, you will need a "superuser". To create it, 6. Run `docker compose up -d`. This will create and start the necessary containers.
execute the following command:
```shell-session 7. Congratulations! Your Paperless-ngx instance should now be accessible at `http://127.0.0.1:8000`
docker compose run --rm webserver createsuperuser (or similar, depending on your configuration). When you first access the web interface, you will be
``` prompted to create a superuser account.
or using docker exec from within the container:
```shell-session
python3 manage.py createsuperuser
```
This will guide you through the superuser setup.
7. Run `docker compose up -d`. This will create and start the necessary containers.
8. Congratulations! Your Paperless-ngx instance should now be accessible at `http://127.0.0.1:8000`
(or similar, depending on your configuration). Use the superuser credentials you have
created in the previous step to login.
### Build the Docker image yourself {#docker_build} ### Build the Docker image yourself {#docker_build}
@@ -386,16 +371,15 @@ are released, dependency support is confirmed, etc.
dependencies for Postgres or Mariadb. You can select those extras with `--extra <EXTRA>` dependencies for Postgres or Mariadb. You can select those extras with `--extra <EXTRA>`
or all with `--all-extras` or all with `--all-extras`
9. Go to `/opt/paperless/src`, and execute the following commands: 9. Go to `/opt/paperless/src`, and execute the following command:
```bash ```bash
# This creates the database schema. # This creates the database schema.
sudo -Hu paperless python3 manage.py migrate sudo -Hu paperless python3 manage.py migrate
# This creates your first paperless user
sudo -Hu paperless python3 manage.py createsuperuser
``` ```
When you first access the web interface you will be prompted to create a superuser account.
10. Optional: Test that paperless is working by executing 10. Optional: Test that paperless is working by executing
```bash ```bash
@@ -708,7 +692,8 @@ Paperless runs on Raspberry Pi. However, some things are rather slow on
the Pi and configuring some options in paperless can help improve the Pi and configuring some options in paperless can help improve
performance immensely: performance immensely:
- Stick with SQLite to save some resources. - Stick with SQLite to save some resources. See [troubleshooting](troubleshooting.md#log-reports-creating-paperlesstask-failed)
if you encounter issues with SQLite locking.
- If you do not need the filesystem-based consumer, consider disabling it - If you do not need the filesystem-based consumer, consider disabling it
entirely by setting [`PAPERLESS_CONSUMER_DISABLE`](configuration.md#PAPERLESS_CONSUMER_DISABLE) to `true`. entirely by setting [`PAPERLESS_CONSUMER_DISABLE`](configuration.md#PAPERLESS_CONSUMER_DISABLE) to `true`.
- Consider setting [`PAPERLESS_OCR_PAGES`](configuration.md#PAPERLESS_OCR_PAGES) to 1, so that paperless will - Consider setting [`PAPERLESS_OCR_PAGES`](configuration.md#PAPERLESS_OCR_PAGES) to 1, so that paperless will

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
@@ -292,7 +292,9 @@ many workers attempting to access the database simultaneously.
Consider changing to the PostgreSQL database if you will be processing Consider changing to the PostgreSQL database if you will be processing
many documents at once often. Otherwise, try tweaking the many documents at once often. Otherwise, try tweaking the
[`PAPERLESS_DB_TIMEOUT`](configuration.md#PAPERLESS_DB_TIMEOUT) setting to allow more time for the database to [`PAPERLESS_DB_TIMEOUT`](configuration.md#PAPERLESS_DB_TIMEOUT) setting to allow more time for the database to
unlock. This may have minor performance implications. unlock. Additionally, you can change your SQLite database to use ["Write-Ahead Logging"](https://sqlite.org/wal.html).
These changes may have minor performance implications but can help
prevent database locking issues.
## granian fails to start with "is not a valid port number" ## granian fails to start with "is not a valid port number"

View File

@@ -89,7 +89,7 @@ and more. These areas allow you to view, add, edit, delete and manage permission
for these objects. You can also manage saved views, mail accounts, mail rules, for these objects. You can also manage saved views, mail accounts, mail rules,
workflows and more from the management sections. workflows and more from the management sections.
## Adding documents to paperless ## Adding documents to Paperless-ngx
Once you've got Paperless setup, you need to start feeding documents Once you've got Paperless setup, you need to start feeding documents
into it. When adding documents to paperless, it will perform the into it. When adding documents to paperless, it will perform the
@@ -115,7 +115,8 @@ following operations on your documents:
No matter which options you choose, Paperless will always store the No matter which options you choose, Paperless will always store the
original document that it found in the consumption directory or in the original document that it found in the consumption directory or in the
mail and will never overwrite that document. Archived versions are mail and will never overwrite that document (except when using certain
document actions, which make that clear). Archived versions are
stored alongside the original versions. Any files found in the stored alongside the original versions. Any files found in the
consumption directory will stored inside the Paperless-ngx file consumption directory will stored inside the Paperless-ngx file
structure and will not be retained in the consumption directory. structure and will not be retained in the consumption directory.
@@ -159,7 +160,7 @@ process.
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Related-Projects) for a user-maintained list of related projects and Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Related-Projects) for a user-maintained list of related projects and
software (e.g. for mobile devices) that is compatible with Paperless-ngx. software (e.g. for mobile devices) that is compatible with Paperless-ngx.
### Email {#usage-email} ### Incoming Email {#incoming-mail}
You can tell paperless-ngx to consume documents from your email You can tell paperless-ngx to consume documents from your email
accounts. This is a very flexible and powerful feature, if you regularly accounts. This is a very flexible and powerful feature, if you regularly
@@ -260,6 +261,31 @@ Once setup, navigating to the email settings page in Paperless-ngx will allow yo
You can also submit a document using the REST API, see [POSTing documents](api.md#file-uploads) You can also submit a document using the REST API, see [POSTing documents](api.md#file-uploads)
for details. for details.
## Sharing documents from Paperless-ngx
Paperless-ngx supports sharing documents with other users by assigning them [permissions](#object-permissions)
to the document. Document files can also be shared externally via [share links](#share-links), [email](#email-sharing)
or using [email](#workflow-action-email) or [webhook](#workflow-action-webhook) actions in workflows.
### Share Links
"Share links" are shareable public links to files and can be created and managed under the 'Send' button on the document detail screen.
- Share links do not require a user to login and thus link directly to a file.
- Links are unique and are of the form `{paperless-url}/share/{randomly-generated-slug}`.
- Links can optionally have an expiration time set.
- After a link expires or is deleted users will be redirected to the regular paperless-ngx login.
!!! tip
If your paperless-ngx instance is behind a reverse-proxy you may want to create an exception to bypass any authentication layers that are part of your setup in order to make links truly publicly-accessible. Of course, do so with caution.
### Email Sharing {#email-sharing}
Paperless-ngx supports directly sending documents via email. If an email server has been [configured](configuration.md#email-sending)
the "Send" button on the document detail page will include an "Email" option. You can also share files via email automatically by using
a [workflow action](#workflow-action-email).
## Permissions ## Permissions
Permissions in Paperless-ngx are based around ['global' permissions](#global-permissions) as well as Permissions in Paperless-ngx are based around ['global' permissions](#global-permissions) as well as
@@ -312,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}
@@ -369,7 +395,7 @@ fields and permissions, which will be merged.
### Workflow Triggers ### Workflow Triggers
#### Types #### Types {#workflow-trigger-types}
Currently, there are three events that correspond to workflow trigger 'types': Currently, there are three events that correspond to workflow trigger 'types':
@@ -381,7 +407,8 @@ Currently, there are three events that correspond to workflow trigger 'types':
3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching, 3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching,
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. 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 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:
@@ -429,11 +456,11 @@ Workflows allow you to filter by:
### Workflow Actions ### Workflow Actions
#### Types #### Types {#workflow-action-types}
The following workflow action types are available: The following workflow action types are available:
##### Assignment ##### Assignment {#workflow-action-assignment}
"Assignment" actions can assign: "Assignment" actions can assign:
@@ -443,7 +470,7 @@ The following workflow action types are available:
- View and / or edit permissions to users or groups - View and / or edit permissions to users or groups
- Custom fields. Note that no value for the field will be set - Custom fields. Note that no value for the field will be set
##### Removal ##### Removal {#workflow-action-removal}
"Removal" actions can remove either all of or specific sets of the following: "Removal" actions can remove either all of or specific sets of the following:
@@ -452,7 +479,7 @@ The following workflow action types are available:
- View and / or edit permissions - View and / or edit permissions
- Custom fields - Custom fields
##### Email ##### Email {#workflow-action-email}
"Email" actions can send documents via email. This action requires a mail server to be [configured](configuration.md#email-sending). You can specify: "Email" actions can send documents via email. This action requires a mail server to be [configured](configuration.md#email-sending). You can specify:
@@ -460,7 +487,7 @@ The following workflow action types are available:
- The subject and body of the email, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below - The subject and body of the email, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below
- Whether to include the document as an attachment - Whether to include the document as an attachment
##### Webhook ##### Webhook {#workflow-action-webhook}
"Webhook" actions send a POST request to a specified URL. You can specify: "Webhook" actions send a POST request to a specified URL. You can specify:
@@ -506,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).
@@ -544,19 +571,6 @@ The following custom field types are supported:
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse - `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
- `Select`: a pre-defined list of strings from which the user can choose - `Select`: a pre-defined list of strings from which the user can choose
## Share Links
Paperless-ngx added the ability to create shareable links to files in version 2.0. You can find the button for this on the document detail screen.
- Share links do not require a user to login and thus link directly to a file.
- Links are unique and are of the form `{paperless-url}/share/{randomly-generated-slug}`.
- Links can optionally have an expiration time set.
- After a link expires or is deleted users will be redirected to the regular paperless-ngx login.
!!! tip
If your paperless-ngx instance is behind a reverse-proxy you may want to create an exception to bypass any authentication layers that are part of your setup in order to make links truly publicly-accessible. Of course, do so with caution.
## PDF Actions ## PDF Actions
Paperless-ngx supports four basic editing operations for PDFs (these operations currently cannot be performed on non-PDF files): Paperless-ngx supports four basic editing operations for PDFs (these operations currently cannot be performed on non-PDF files):

View File

@@ -11,14 +11,12 @@ theme:
toggle: toggle:
icon: material/brightness-auto icon: material/brightness-auto
name: Switch to light mode name: Switch to light mode
# Palette toggle for light mode # Palette toggle for light mode
- media: "(prefers-color-scheme: light)" - media: "(prefers-color-scheme: light)"
scheme: default scheme: default
toggle: toggle:
icon: material/brightness-7 icon: material/brightness-7
name: Switch to dark mode name: Switch to dark mode
# Palette toggle for dark mode # Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)" - media: "(prefers-color-scheme: dark)"
scheme: slate scheme: slate
@@ -60,17 +58,17 @@ markdown_extensions:
emoji_generator: !!python/name:material.extensions.emoji.to_svg emoji_generator: !!python/name:material.extensions.emoji.to_svg
strict: true strict: true
nav: nav:
- index.md - index.md
- setup.md - setup.md
- 'Basic Usage': usage.md - 'Basic Usage': usage.md
- configuration.md - configuration.md
- administration.md - administration.md
- advanced_usage.md - advanced_usage.md
- 'REST API': api.md - 'REST API': api.md
- development.md - development.md
- 'FAQs': faq.md - 'FAQs': faq.md
- troubleshooting.md - troubleshooting.md
- changelog.md - changelog.md
copyright: Copyright &copy; 2016 - 2023 Daniel Quinn, Jonas Winkler, and the Paperless-ngx team copyright: Copyright &copy; 2016 - 2023 Daniel Quinn, Jonas Winkler, and the Paperless-ngx team
extra: extra:
social: social:
@@ -83,5 +81,5 @@ extra:
plugins: plugins:
- search - search
- glightbox: - glightbox:
skip_classes: skip_classes:
- no-lightbox - no-lightbox

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.15.0" 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"
@@ -16,20 +16,20 @@ classifiers = [
dependencies = [ dependencies = [
"bleach~=6.2.0", "bleach~=6.2.0",
"celery[redis]~=5.4.0", "celery[redis]~=5.5.1",
"channels~=4.2", "channels~=4.2",
"channels-redis~=4.2", "channels-redis~=4.2",
"concurrent-log-handler~=0.9.25", "concurrent-log-handler~=0.9.25",
"dateparser~=1.2", "dateparser~=1.2",
# WARNING: django does not use semver. # WARNING: django does not use semver.
# Only patch versions are guaranteed to not introduce breaking changes. # Only patch versions are guaranteed to not introduce breaking changes.
"django~=5.1.6", "django~=5.1.7",
"django-allauth[socialaccount,mfa]~=65.4.0", "django-allauth[socialaccount,mfa]~=65.4.0",
"django-auditlog~=3.0.0", "django-auditlog~=3.1.2",
"django-celery-results~=2.5.1", "django-celery-results~=2.6.0",
"django-compression-middleware~=0.5.0", "django-compression-middleware~=0.5.0",
"django-cors-headers~=4.7.0", "django-cors-headers~=4.7.0",
"django-extensions~=3.2.3", "django-extensions~=4.1",
"django-filter~=25.1", "django-filter~=25.1",
"django-guardian~=2.4.0", "django-guardian~=2.4.0",
"django-multiselectfield~=0.1.13", "django-multiselectfield~=0.1.13",
@@ -37,11 +37,11 @@ dependencies = [
"djangorestframework~=3.15", "djangorestframework~=3.15",
"djangorestframework-guardian~=0.3.0", "djangorestframework-guardian~=0.3.0",
"drf-spectacular~=0.28", "drf-spectacular~=0.28",
"drf-spectacular-sidecar~=2025.3.1", "drf-spectacular-sidecar~=2025.4.1",
"drf-writable-nested~=0.7.1", "drf-writable-nested~=0.7.1",
"filelock~=3.17.0", "filelock~=3.18.0",
"flower~=2.0.1", "flower~=2.0.1",
"gotenberg-client~=0.9.0", "gotenberg-client~=0.10.0",
"httpx-oauth~=0.16", "httpx-oauth~=0.16",
"imap-tools~=1.10.0", "imap-tools~=1.10.0",
"inotifyrecursive~=0.3", "inotifyrecursive~=0.3",
@@ -52,12 +52,12 @@ dependencies = [
"pathvalidate~=3.2.3", "pathvalidate~=3.2.3",
"pdf2image~=1.17.0", "pdf2image~=1.17.0",
"python-dateutil~=2.9.0", "python-dateutil~=2.9.0",
"python-dotenv~=1.0.1", "python-dotenv~=1.1.0",
"python-gnupg~=0.5.4", "python-gnupg~=0.5.4",
"python-ipware~=3.0.0", "python-ipware~=3.0.0",
"python-magic~=0.4.27", "python-magic~=0.4.27",
"pyzbar~=0.1.9", "pyzbar~=0.1.9",
"rapidfuzz~=3.12.1", "rapidfuzz~=3.13.0",
"redis[hiredis]~=5.2.1", "redis[hiredis]~=5.2.1",
"scikit-learn~=1.6.1", "scikit-learn~=1.6.1",
"setproctitle~=1.3.4", "setproctitle~=1.3.4",
@@ -65,7 +65,7 @@ dependencies = [
"tqdm~=4.67.1", "tqdm~=4.67.1",
"watchdog~=6.0", "watchdog~=6.0",
"whitenoise~=6.9", "whitenoise~=6.9",
"whoosh~=2.7", "whoosh-reloaded>=2.7.5",
"zxing-cpp~=2.3.0", "zxing-cpp~=2.3.0",
] ]
@@ -78,7 +78,7 @@ optional-dependencies.postgres = [
"psycopg-c==3.2.5", "psycopg-c==3.2.5",
] ]
optional-dependencies.webserver = [ optional-dependencies.webserver = [
"granian~=2.0.1", "granian[uvloop]~=2.3.2",
] ]
[dependency-groups] [dependency-groups]
@@ -221,52 +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_management.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_management_consumer.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_management_exporter.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/tests/test_migration_document_pages_count.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_migration_mime_type.py" = [
"PTH",
] # TODO Enable & remove
lint.per-file-ignores."src/documents/tests/test_sanity_check.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

@@ -0,0 +1,13 @@
export const getDocument = jest.fn(() => ({
promise: Promise.resolve({ numPages: 3 }),
}))
export const GlobalWorkerOptions = { workerSrc: '' }
export const VerbosityLevel = { ERRORS: 0 }
globalThis.pdfjsLib = {
getDocument,
GlobalWorkerOptions,
VerbosityLevel,
AbortException: class AbortException extends Error {},
}

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",

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,31 +1,30 @@
{ {
"name": "paperless-ui", "name": "paperless-ngx-ui",
"version": "0.0.0", "version": "2.17.1",
"scripts": { "scripts": {
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve",
"build": "ng build", "build": "ng build",
"test": "ng test --no-watch --coverage", "test": "ng test --no-watch --coverage",
"lint": "ng lint", "lint": "ng lint"
"postinstall": "patch-package"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/cdk": "^19.2.2", "@angular/cdk": "^19.2.14",
"@angular/common": "~19.2.1", "@angular/common": "~19.2.14",
"@angular/compiler": "~19.2.1", "@angular/compiler": "~19.2.14",
"@angular/core": "~19.2.1", "@angular/core": "~19.2.14",
"@angular/forms": "~19.2.1", "@angular/forms": "~19.2.14",
"@angular/localize": "~19.2.1", "@angular/localize": "~19.2.14",
"@angular/platform-browser": "~19.2.1", "@angular/platform-browser": "~19.2.14",
"@angular/platform-browser-dynamic": "~19.2.1", "@angular/platform-browser-dynamic": "~19.2.14",
"@angular/router": "~19.2.1", "@angular/router": "~19.2.14",
"@ng-bootstrap/ng-bootstrap": "^18.0.0", "@ng-bootstrap/ng-bootstrap": "^18.0.0",
"@ng-select/ng-select": "^14.2.3", "@ng-select/ng-select": "^14.9.0",
"@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",
@@ -33,44 +32,43 @@
"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-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^16.0.0", "ngx-ui-tour-ng-bootstrap": "^16.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.0", "@angular-builders/custom-webpack": "^19.0.1",
"@angular-builders/jest": "^19.0.0", "@angular-builders/jest": "^19.0.1",
"@angular-devkit/build-angular": "^19.2.1", "@angular-devkit/build-angular": "^19.2.14",
"@angular-devkit/core": "^19.2.1", "@angular-devkit/core": "^19.2.14",
"@angular-devkit/schematics": "^19.2.1", "@angular-devkit/schematics": "^19.2.14",
"@angular-eslint/builder": "19.2.1", "@angular-eslint/builder": "19.7.0",
"@angular-eslint/eslint-plugin": "19.2.1", "@angular-eslint/eslint-plugin": "19.7.0",
"@angular-eslint/eslint-plugin-template": "19.2.1", "@angular-eslint/eslint-plugin-template": "19.7.0",
"@angular-eslint/schematics": "19.2.1", "@angular-eslint/schematics": "19.7.0",
"@angular-eslint/template-parser": "19.2.1", "@angular-eslint/template-parser": "19.7.0",
"@angular/cli": "~19.2.1", "@angular/cli": "~19.2.14",
"@angular/compiler-cli": "~19.2.1", "@angular/compiler-cli": "~19.2.14",
"@codecov/webpack-plugin": "^1.9.0", "@codecov/webpack-plugin": "^1.9.1",
"@playwright/test": "^1.50.1", "@playwright/test": "^1.51.1",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"@types/node": "^22.13.9", "@types/node": "^22.15.29",
"@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/eslint-plugin": "^8.33.0",
"@typescript-eslint/parser": "^8.26.1", "@typescript-eslint/parser": "^8.33.0",
"@typescript-eslint/utils": "^8.26.1", "@typescript-eslint/utils": "^8.33.0",
"eslint": "^9.22.0", "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",
"jest-preset-angular": "^14.5.3", "jest-preset-angular": "^14.5.5",
"jest-websocket-mock": "^2.5.0", "jest-websocket-mock": "^2.5.0",
"patch-package": "^8.0.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.5.4",
"webpack": "^5.98.0"
}, },
"pnpm": { "pnpm": {
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [

File diff suppressed because one or more lines are too long

4424
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)
@@ -121,19 +123,4 @@ HTMLCanvasElement.prototype.getContext = <
typeof HTMLCanvasElement.prototype.getContext typeof HTMLCanvasElement.prototype.getContext
>jest.fn() >jest.fn()
// pdfjs jest.mock('pdfjs-dist')
jest.mock('pdfjs-dist', () => ({
getDocument: jest.fn(() => ({
promise: Promise.resolve({ numPages: 3 }),
})),
GlobalWorkerOptions: { workerSrc: '' },
VerbosityLevel: { ERRORS: 0 },
globalThis: {
pdfjsLib: {
GlobalWorkerOptions: {
workerSrc: '',
},
},
},
}))
jest.mock('pdfjs-dist/web/pdf_viewer', () => ({}))

View File

@@ -9,7 +9,6 @@ import {
import { Router, RouterModule } from '@angular/router' import { Router, RouterModule } from '@angular/router'
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap' import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { NgxFileDropModule } from 'ngx-file-drop'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { routes } from './app-routing.module' import { routes } from './app-routing.module'
@@ -43,7 +42,6 @@ describe('AppComponent', () => {
imports: [ imports: [
TourNgBootstrapModule, TourNgBootstrapModule,
RouterModule.forRoot(routes), RouterModule.forRoot(routes),
NgxFileDropModule,
NgbModalModule, NgbModalModule,
AppComponent, AppComponent,
ToastsComponent, ToastsComponent,

View File

@@ -105,9 +105,9 @@ describe('ConfigComponent', () => {
it('should support JSON validation for e.g. user_args', () => { it('should support JSON validation for e.g. user_args', () => {
component.configForm.patchValue({ user_args: '{ foo bar }' }) component.configForm.patchValue({ user_args: '{ foo bar }' })
expect(component.errors).toEqual({ user_args: 'Invalid JSON' }) expect(component.errors['user_args']).toEqual('Invalid JSON')
component.configForm.patchValue({ user_args: '{ "foo": "bar" }' }) component.configForm.patchValue({ user_args: '{ "foo": "bar" }' })
expect(component.errors).toEqual({ user_args: null }) expect(component.errors['user_args']).toBeNull()
}) })
it('should upload file, show error if necessary', () => { it('should upload file, show error if necessary', () => {

View File

@@ -15,7 +15,7 @@
</svg> </svg>
<div class="ms-2 ms-md-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled"> <div class="ms-2 ms-md-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled">
@if (customAppTitle?.length) { @if (customAppTitle?.length) {
<div class="d-flex flex-column align-items-start"> <div class="d-flex flex-column align-items-start custom-title">
<span class="title">{{customAppTitle}}</span> <span class="title">{{customAppTitle}}</span>
<span class="byline text-uppercase font-monospace" i18n>by Paperless-ngx</span> <span class="byline text-uppercase font-monospace" i18n>by Paperless-ngx</span>
</div> </div>

View File

@@ -244,7 +244,7 @@ main {
} }
} }
@media screen and (max-width: 768px) { @media screen and (min-width: 366px) and (max-width: 768px) {
.navbar-toggler { .navbar-toggler {
// compensate for 2 buttons on the right // compensate for 2 buttons on the right
margin-right: 45px; margin-right: 45px;
@@ -257,6 +257,13 @@ main {
} }
} }
@media screen and (max-width: 345px) {
.custom-title {
max-width: 110px;
overflow: hidden;
}
}
:host ::ng-deep .dropdown.show .dropdown-toggle, :host ::ng-deep .dropdown.show .dropdown-toggle,
:host ::ng-deep .dropdown-toggle:hover { :host ::ng-deep .dropdown-toggle:hover {
opacity: 0.7; opacity: 0.7;

View File

@@ -74,7 +74,6 @@ export class AppFrameComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, ComponentCanDeactivate implements OnInit, ComponentCanDeactivate
{ {
versionString = `${environment.appTitle} ${environment.version}`
appRemoteVersion: AppRemoteVersion appRemoteVersion: AppRemoteVersion
isMenuCollapsed: boolean = true isMenuCollapsed: boolean = true
@@ -142,6 +141,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)
} }

View File

@@ -89,7 +89,7 @@
@if (searchResults?.documents.length) { @if (searchResults?.documents.length) {
<h6 class="dropdown-header" i18n="@@searchResults.documents">Documents</h6> <h6 class="dropdown-header" i18n="@@searchResults.documents">Documents</h6>
@for (document of searchResults.documents; track document.id) { @for (document of searchResults.documents; track document.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: DataType.Document, icon: 'file-text', date: document.added}"></ng-container> <ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: document, nameProp: 'title', type: DataType.Document, icon: 'file-text', date: document.created}"></ng-container>
} }
} }
@if (searchResults?.saved_views.length) { @if (searchResults?.saved_views.length) {

View File

@@ -405,7 +405,7 @@ describe('GlobalSearchComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled() expect(toastErrorSpy).toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(true) editDialog.succeeded.emit(object as any)
expect(toastInfoSpy).toHaveBeenCalled() expect(toastInfoSpy).toHaveBeenCalled()
}) })
@@ -456,7 +456,7 @@ describe('GlobalSearchComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled() expect(toastErrorSpy).toHaveBeenCalled()
// succeed // succeed
editDialog.succeeded.emit(true) editDialog.succeeded.emit(searchResults.tags[0] as any)
expect(toastInfoSpy).toHaveBeenCalled() expect(toastInfoSpy).toHaveBeenCalled()
}) })
@@ -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,
@@ -99,7 +99,8 @@ export class GlobalSearchComponent implements OnInit {
private permissionsService: PermissionsService, private permissionsService: PermissionsService,
private toastService: ToastService, private toastService: ToastService,
private hotkeyService: HotKeyService, private hotkeyService: HotKeyService,
private settingsService: SettingsService private settingsService: SettingsService,
private locationStrategy: LocationStrategy
) { ) {
this.queryDebounce = new Subject<string>() this.queryDebounce = new Subject<string>()
@@ -421,10 +422,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 @@
<div ngbDropdown #fieldDropdown="ngbDropdown" (openChange)="onOpenClose($event)"> <div ngbDropdown #fieldDropdown="ngbDropdown" (openChange)="onOpenClose($event)" [popperOptions]="popperOptions" placement="bottom-end">
<button class="btn btn-sm btn-outline-primary" id="customFieldsDropdown" [disabled]="disabled" ngbDropdownToggle> <button class="btn btn-sm btn-outline-primary" id="customFieldsDropdown" [disabled]="disabled" ngbDropdownToggle>
<i-bs name="ui-radios"></i-bs> <i-bs name="ui-radios"></i-bs>
<div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Custom Fields</ng-container></div> <div class="d-none d-sm-inline">&nbsp;<ng-container i18n>Custom Fields</ng-container></div>

View File

@@ -21,6 +21,7 @@ import {
} from 'src/app/services/permissions.service' } from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service' import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { pngxPopperOptions } from 'src/app/utils/popper-options'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@@ -36,6 +37,8 @@ import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit
], ],
}) })
export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissions { export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissions {
public popperOptions = pngxPopperOptions
@Input() @Input()
documentId: number documentId: number

View File

@@ -47,7 +47,7 @@ export abstract class EditDialogComponent<
object: T object: T
@Output() @Output()
succeeded = new EventEmitter() succeeded = new EventEmitter<T>()
@Output() @Output()
failed = new EventEmitter() failed = new EventEmitter()

View File

@@ -123,7 +123,15 @@
<p class="small" i18n>Set scheduled trigger offset and which date field to use.</p> <p class="small" i18n>Set scheduled trigger offset and which date field to use.</p>
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4">
<pngx-input-number i18n-title title="Offset days" formControlName="schedule_offset_days" [showAdd]="false" [error]="error?.schedule_offset_days"></pngx-input-number> <pngx-input-number
i18n-title
title="Offset days"
formControlName="schedule_offset_days"
[showAdd]="false"
[error]="error?.schedule_offset_days"
hint="Positive values will trigger after the date, negative values before."
i18n-hint
></pngx-input-number>
</div> </div>
<div class="col-4"> <div class="col-4">
<pngx-input-select i18n-title title="Relative to" formControlName="schedule_date_field" [items]="scheduleDateFieldOptions" [error]="error?.schedule_date_field"></pngx-input-select> <pngx-input-select i18n-title title="Relative to" formControlName="schedule_date_field" [items]="scheduleDateFieldOptions" [error]="error?.schedule_date_field"></pngx-input-select>

View File

@@ -62,6 +62,7 @@ export class EmailDocumentDialogComponent extends LoadingComponentWithPermission
this.emailAddress = '' this.emailAddress = ''
this.emailSubject = '' this.emailSubject = ''
this.emailMessage = '' this.emailMessage = ''
this.close()
this.toastService.showInfo($localize`Email sent`) this.toastService.showInfo($localize`Email sent`)
}, },
error: (e) => { error: (e) => {

View File

@@ -7,6 +7,7 @@ import {
tick, tick,
} from '@angular/core/testing' } from '@angular/core/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { NEGATIVE_NULL_FILTER_VALUE } from 'src/app/data/filter-rule-type'
import { import {
DEFAULT_MATCHING_ALGORITHM, DEFAULT_MATCHING_ALGORITHM,
MATCH_ALL, MATCH_ALL,
@@ -44,6 +45,11 @@ const nullItem = {
name: 'Not assigned', name: 'Not assigned',
} }
const negativeNullItem = {
id: NEGATIVE_NULL_FILTER_VALUE,
name: 'Not assigned',
}
let selectionModel: FilterableDropdownSelectionModel let selectionModel: FilterableDropdownSelectionModel
describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () => { describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () => {
@@ -64,6 +70,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
hotkeyService = TestBed.inject(HotKeyService) hotkeyService = TestBed.inject(HotKeyService)
fixture = TestBed.createComponent(FilterableDropdownComponent) fixture = TestBed.createComponent(FilterableDropdownComponent)
component = fixture.componentInstance component = fixture.componentInstance
component.selectionModel = new FilterableDropdownSelectionModel()
selectionModel = new FilterableDropdownSelectionModel() selectionModel = new FilterableDropdownSelectionModel()
}) })
@@ -74,7 +81,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should support reset', () => { it('should support reset', () => {
component.items = items component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
selectionModel.set(items[0].id, ToggleableItemState.Selected) selectionModel.set(items[0].id, ToggleableItemState.Selected)
expect(selectionModel.getSelectedItems()).toHaveLength(1) expect(selectionModel.getSelectedItems()).toHaveLength(1)
@@ -96,7 +103,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should emit change when items selected', () => { it('should emit change when items selected', () => {
component.items = items component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
let newModel: FilterableDropdownSelectionModel let newModel: FilterableDropdownSelectionModel
component.selectionModelChange.subscribe((model) => (newModel = model)) component.selectionModelChange.subscribe((model) => (newModel = model))
@@ -110,11 +117,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
selectionModel.set(items[0].id, ToggleableItemState.NotSelected) selectionModel.set(items[0].id, ToggleableItemState.NotSelected)
expect(newModel.getSelectedItems()).toEqual([]) expect(newModel.getSelectedItems()).toEqual([])
expect(component.items).toEqual([nullItem, ...items]) expect(component.selectionModel.items).toEqual([nullItem, ...items])
}) })
it('should emit change when items excluded', () => { it('should emit change when items excluded', () => {
component.items = items component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
let newModel: FilterableDropdownSelectionModel let newModel: FilterableDropdownSelectionModel
component.selectionModelChange.subscribe((model) => (newModel = model)) component.selectionModelChange.subscribe((model) => (newModel = model))
@@ -124,7 +131,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should emit change when items excluded', () => { it('should emit change when items excluded', () => {
component.items = items component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
let newModel: FilterableDropdownSelectionModel let newModel: FilterableDropdownSelectionModel
component.selectionModelChange.subscribe((model) => (newModel = model)) component.selectionModelChange.subscribe((model) => (newModel = model))
@@ -139,8 +146,8 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should exclude items when excluded and not editing', () => { it('should exclude items when excluded and not editing', () => {
component.items = items component.selectionModel.items = items
component.manyToOne = true component.selectionModel.manyToOne = true
component.selectionModel = selectionModel component.selectionModel = selectionModel
selectionModel.set(items[0].id, ToggleableItemState.Selected) selectionModel.set(items[0].id, ToggleableItemState.Selected)
component.excludeClicked(items[0].id) component.excludeClicked(items[0].id)
@@ -149,8 +156,8 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should toggle when items excluded and editing', () => { it('should toggle when items excluded and editing', () => {
component.items = items component.selectionModel.items = items
component.manyToOne = true component.selectionModel.manyToOne = true
component.editing = true component.editing = true
component.selectionModel = selectionModel component.selectionModel = selectionModel
selectionModel.set(items[0].id, ToggleableItemState.NotSelected) selectionModel.set(items[0].id, ToggleableItemState.NotSelected)
@@ -160,8 +167,8 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should hide count for item if adding will increase size of set', () => { it('should hide count for item if adding will increase size of set', () => {
component.items = items component.selectionModel.items = items
component.manyToOne = true component.selectionModel.manyToOne = true
component.selectionModel = selectionModel component.selectionModel = selectionModel
expect(component.hideCount(items[0])).toBeFalsy() expect(component.hideCount(items[0])).toBeFalsy()
selectionModel.logicalOperator = LogicalOperator.Or selectionModel.logicalOperator = LogicalOperator.Or
@@ -170,7 +177,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
it('should enforce single select when editing', () => { it('should enforce single select when editing', () => {
component.editing = true component.editing = true
component.items = items component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
let newModel: FilterableDropdownSelectionModel let newModel: FilterableDropdownSelectionModel
component.selectionModelChange.subscribe((model) => (newModel = model)) component.selectionModelChange.subscribe((model) => (newModel = model))
@@ -182,11 +189,11 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should support manyToOne selecting', () => { it('should support manyToOne selecting', () => {
component.items = items component.selectionModel.items = items
selectionModel.manyToOne = false selectionModel.manyToOne = false
component.selectionModel = selectionModel component.selectionModel = selectionModel
component.manyToOne = true component.selectionModel.manyToOne = true
expect(component.manyToOne).toBeTruthy() expect(component.selectionModel.manyToOne).toBeTruthy()
let newModel: FilterableDropdownSelectionModel let newModel: FilterableDropdownSelectionModel
component.selectionModelChange.subscribe((model) => (newModel = model)) component.selectionModelChange.subscribe((model) => (newModel = model))
@@ -197,12 +204,10 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should dynamically enable / disable modifier toggle', () => { it('should dynamically enable / disable modifier toggle', () => {
component.items = items component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
expect(component.modifierToggleEnabled).toBeTruthy() expect(component.modifierToggleEnabled).toBeTruthy()
selectionModel.toggle(null) component.selectionModel.manyToOne = true
expect(component.modifierToggleEnabled).toBeFalsy()
component.manyToOne = true
expect(component.modifierToggleEnabled).toBeFalsy() expect(component.modifierToggleEnabled).toBeFalsy()
selectionModel.toggle(items[0].id) selectionModel.toggle(items[0].id)
selectionModel.toggle(items[1].id) selectionModel.toggle(items[1].id)
@@ -210,7 +215,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should apply changes and close when apply button clicked', () => { it('should apply changes and close when apply button clicked', () => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.editing = true component.editing = true
component.selectionModel = selectionModel component.selectionModel = selectionModel
@@ -232,7 +237,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should apply on close if enabled', () => { it('should apply on close if enabled', () => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.editing = true component.editing = true
component.applyOnClose = true component.applyOnClose = true
@@ -250,7 +255,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should focus text filter on open, support filtering, clear on close', fakeAsync(() => { it('should focus text filter on open, support filtering, clear on close', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
fixture.nativeElement fixture.nativeElement
.querySelector('button') .querySelector('button')
@@ -277,7 +282,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should toggle & close on enter inside filter field if 1 item remains', fakeAsync(() => { it('should toggle & close on enter inside filter field if 1 item remains', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
expect(component.selectionModel.getSelectedItems()).toEqual([]) expect(component.selectionModel.getSelectedItems()).toEqual([])
fixture.nativeElement fixture.nativeElement
@@ -297,7 +302,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should apply & close on enter inside filter field if 1 item remains if editing', fakeAsync(() => { it('should apply & close on enter inside filter field if 1 item remains if editing', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.editing = true component.editing = true
let applyResult: ChangedItems let applyResult: ChangedItems
@@ -319,7 +324,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should support arrow keyboard navigation', fakeAsync(() => { it('should support arrow keyboard navigation', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
fixture.nativeElement fixture.nativeElement
.querySelector('button') .querySelector('button')
@@ -364,7 +369,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should support arrow keyboard navigation after tab keyboard navigation', fakeAsync(() => { it('should support arrow keyboard navigation after tab keyboard navigation', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
fixture.nativeElement fixture.nativeElement
.querySelector('button') .querySelector('button')
@@ -400,7 +405,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should support arrow keyboard navigation after click', fakeAsync(() => { it('should support arrow keyboard navigation after click', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
fixture.nativeElement fixture.nativeElement
.querySelector('button') .querySelector('button')
@@ -425,9 +430,9 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should toggle logical operator', fakeAsync(() => { it('should toggle logical operator', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.manyToOne = true component.selectionModel.manyToOne = true
selectionModel.set(items[0].id, ToggleableItemState.Selected) selectionModel.set(items[0].id, ToggleableItemState.Selected)
selectionModel.set(items[1].id, ToggleableItemState.Selected) selectionModel.set(items[1].id, ToggleableItemState.Selected)
component.selectionModel = selectionModel component.selectionModel = selectionModel
@@ -454,7 +459,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should toggle intersection include / exclude', fakeAsync(() => { it('should toggle intersection include / exclude', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
selectionModel.set(items[0].id, ToggleableItemState.Selected) selectionModel.set(items[0].id, ToggleableItemState.Selected)
selectionModel.set(items[1].id, ToggleableItemState.Selected) selectionModel.set(items[1].id, ToggleableItemState.Selected)
@@ -483,22 +488,53 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
expect(changedResult.getExcludedItems()).toEqual(items) expect(changedResult.getExcludedItems()).toEqual(items)
})) }))
it('selection model should sort items by state', () => { it('should update null item selection on toggleIntersection', () => {
component.items = items.concat([{ id: null, name: 'Null B' }]) component.selectionModel.items = items
component.selectionModel = selectionModel component.selectionModel = selectionModel
component.selectionModel.intersection = Intersection.Include
component.selectionModel.set(null, ToggleableItemState.Selected)
component.selectionModel.intersection = Intersection.Exclude
component.selectionModel.toggleIntersection()
expect(component.selectionModel.getExcludedItems()).toEqual([
negativeNullItem,
])
component.selectionModel.intersection = Intersection.Include
component.selectionModel.toggleIntersection()
expect(component.selectionModel.getSelectedItems()).toEqual([nullItem])
})
it('selection model should sort items by state', () => {
component.selectionModel = selectionModel
component.selectionModel.items = items.concat([{ id: 3, name: 'Item3' }])
selectionModel.toggle(items[1].id) selectionModel.toggle(items[1].id)
selectionModel.apply() selectionModel.apply()
expect(selectionModel.items.length).toEqual(4)
expect(selectionModel.items).toEqual([ expect(selectionModel.items).toEqual([
nullItem, nullItem,
{ id: null, name: 'Null B' },
items[1], items[1],
{ id: 3, name: 'Item3' },
items[0], items[0],
]) ])
selectionModel.intersection = Intersection.Exclude
selectionModel.toggleIntersection()
selectionModel.apply()
expect(selectionModel.items).toEqual([
negativeNullItem,
items[1],
{ id: 3, name: 'Item3' },
items[0],
])
// coverage
selectionModel.items = selectionModel.items.reverse()
selectionModel.apply()
}) })
it('selection model should sort items by state and document counts = 0, if set', () => { it('selection model should sort items by state and document counts = 0, if set', () => {
const tagA = { id: 4, name: 'Tag A' } const tagA = { id: 4, name: 'Tag A' }
component.items = items.concat([tagA]) component.selectionModel.items = items.concat([tagA])
component.selectionModel = selectionModel component.selectionModel = selectionModel
component.documentCounts = [ component.documentCounts = [
{ id: 1, document_count: 0 }, // Tag1 { id: 1, document_count: 0 }, // Tag1
@@ -529,7 +565,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should set support create, keep open model and call createRef method', fakeAsync(() => { it('should set support create, keep open model and call createRef method', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.selectionModel = selectionModel component.selectionModel = selectionModel
fixture.nativeElement fixture.nativeElement
@@ -549,7 +585,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
})) }))
it('should call create on enter inside filter field if 0 items remain while editing', fakeAsync(() => { it('should call create on enter inside filter field if 0 items remain while editing', fakeAsync(() => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.editing = true component.editing = true
component.createRef = jest.fn() component.createRef = jest.fn()
@@ -569,7 +605,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
const id = 1 const id = 1
const state = ToggleableItemState.Selected const state = ToggleableItemState.Selected
component.selectionModel = selectionModel component.selectionModel = selectionModel
component.manyToOne = true component.selectionModel.manyToOne = true
component.selectionModel.singleSelect = true component.selectionModel.singleSelect = true
component.selectionModel.intersection = Intersection.Include component.selectionModel.intersection = Intersection.Include
component.selectionModel['temporarySelectionStates'].set(id, state) component.selectionModel['temporarySelectionStates'].set(id, state)
@@ -596,7 +632,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should support shortcut keys', () => { it('should support shortcut keys', () => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.shortcutKey = 't' component.shortcutKey = 't'
fixture.detectChanges() fixture.detectChanges()
@@ -606,7 +642,7 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
}) })
it('should support an extra button and not apply changes when clicked', () => { it('should support an extra button and not apply changes when clicked', () => {
component.items = items component.selectionModel.items = items
component.icon = 'tag-fill' component.icon = 'tag-fill'
component.extraButtonTitle = 'Extra' component.extraButtonTitle = 'Extra'
component.selectionModel = selectionModel component.selectionModel = selectionModel

View File

@@ -12,6 +12,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap' import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, filter, takeUntil } from 'rxjs' import { Subject, filter, takeUntil } from 'rxjs'
import { NEGATIVE_NULL_FILTER_VALUE } from 'src/app/data/filter-rule-type'
import { MatchingModel } from 'src/app/data/matching-model' import { MatchingModel } from 'src/app/data/matching-model'
import { ObjectWithPermissions } from 'src/app/data/object-with-permissions' import { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
import { FilterPipe } from 'src/app/pipes/filter.pipe' import { FilterPipe } from 'src/app/pipes/filter.pipe'
@@ -61,15 +62,56 @@ export class FilterableDropdownSelectionModel {
} }
set items(items: MatchingModel[]) { set items(items: MatchingModel[]) {
this._items = items if (items) {
this.sortItems() this._items = Array.from(items)
this.sortItems()
this.setNullItem()
}
}
private setNullItem() {
if (this.manyToOne && this.logicalOperator === LogicalOperator.Or) {
if (this._items[0]?.id === null) {
this._items.shift()
}
return
}
const item = {
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
id:
this.manyToOne || this.intersection === Intersection.Include
? null
: NEGATIVE_NULL_FILTER_VALUE,
}
if (
this._items[0]?.id === null ||
this._items[0]?.id === NEGATIVE_NULL_FILTER_VALUE
) {
this._items[0] = item
} else if (this._items) {
this._items.unshift(item)
}
}
constructor(manyToOne: boolean = false) {
this.manyToOne = manyToOne
} }
private sortItems() { private sortItems() {
this._items.sort((a, b) => { this._items.sort((a, b) => {
if (a.id == null && b.id != null) { if (
(a.id == null && b.id != null) ||
(a.id == NEGATIVE_NULL_FILTER_VALUE &&
b.id != NEGATIVE_NULL_FILTER_VALUE)
) {
return -1 return -1
} else if (a.id != null && b.id == null) { } else if (
(a.id != null && b.id == null) ||
(a.id != NEGATIVE_NULL_FILTER_VALUE &&
b.id == NEGATIVE_NULL_FILTER_VALUE)
) {
return 1 return 1
} else if ( } else if (
this.getNonTemporary(a.id) == ToggleableItemState.NotSelected && this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
@@ -230,6 +272,7 @@ export class FilterableDropdownSelectionModel {
set logicalOperator(operator: LogicalOperator) { set logicalOperator(operator: LogicalOperator) {
this.temporaryLogicalOperator = operator this.temporaryLogicalOperator = operator
this.setNullItem()
} }
toggleOperator() { toggleOperator() {
@@ -242,6 +285,7 @@ export class FilterableDropdownSelectionModel {
set intersection(intersection: Intersection) { set intersection(intersection: Intersection) {
this.temporaryIntersection = intersection this.temporaryIntersection = intersection
this.setNullItem()
} }
toggleIntersection() { toggleIntersection() {
@@ -250,9 +294,20 @@ export class FilterableDropdownSelectionModel {
this.intersection == Intersection.Include this.intersection == Intersection.Include
? ToggleableItemState.Selected ? ToggleableItemState.Selected
: ToggleableItemState.Excluded : ToggleableItemState.Excluded
this.temporarySelectionStates.forEach((state, key) => { this.temporarySelectionStates.forEach((state, key) => {
this.temporarySelectionStates.set(key, newState) if (key === null && this.intersection === Intersection.Exclude) {
this.temporarySelectionStates.set(NEGATIVE_NULL_FILTER_VALUE, newState)
} else if (
key === NEGATIVE_NULL_FILTER_VALUE &&
this.intersection === Intersection.Include
) {
this.temporarySelectionStates.set(null, newState)
} else {
this.temporarySelectionStates.set(key, newState)
}
}) })
this.changed.next(this) this.changed.next(this)
} }
@@ -274,6 +329,7 @@ export class FilterableDropdownSelectionModel {
this.temporarySelectionStates.clear() this.temporarySelectionStates.clear()
this.temporaryLogicalOperator = this._logicalOperator = LogicalOperator.And this.temporaryLogicalOperator = this._logicalOperator = LogicalOperator.And
this.temporaryIntersection = this._intersection = Intersection.Include this.temporaryIntersection = this._intersection = Intersection.Include
this.setNullItem()
if (fireEvent) { if (fireEvent) {
this.changed.next(this) this.changed.next(this)
} }
@@ -305,8 +361,10 @@ export class FilterableDropdownSelectionModel {
isNoneSelected() { isNoneSelected() {
return ( return (
this.selectionSize() == 1 && (this.selectionSize() == 1 &&
this.get(null) == ToggleableItemState.Selected this.get(null) == ToggleableItemState.Selected) ||
(this.intersection == Intersection.Exclude &&
this.get(NEGATIVE_NULL_FILTER_VALUE) == ToggleableItemState.Excluded)
) )
} }
@@ -384,25 +442,13 @@ export class FilterableDropdownComponent
filterText: string filterText: string
@Input() _selectionModel: FilterableDropdownSelectionModel
set items(items: MatchingModel[]) {
if (items) {
this._selectionModel.items = Array.from(items)
this._selectionModel.items.unshift({
name: $localize`:Filter drop down element to filter for documents with no correspondent/type/tag assigned:Not assigned`,
id: null,
})
}
}
get items(): MatchingModel[] { get items(): MatchingModel[] {
return this._selectionModel.items return this._selectionModel.items
} }
_selectionModel: FilterableDropdownSelectionModel = @Input({ required: true })
new FilterableDropdownSelectionModel()
@Input()
set selectionModel(model: FilterableDropdownSelectionModel) { set selectionModel(model: FilterableDropdownSelectionModel) {
if (this.selectionModel) { if (this.selectionModel) {
this.selectionModel.changed.complete() this.selectionModel.changed.complete()
@@ -423,11 +469,6 @@ export class FilterableDropdownComponent
@Output() @Output()
selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>() selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>()
@Input()
set manyToOne(manyToOne: boolean) {
this.selectionModel.manyToOne = manyToOne
}
get manyToOne() { get manyToOne() {
return this.selectionModel.manyToOne return this.selectionModel.manyToOne
} }
@@ -484,7 +525,7 @@ export class FilterableDropdownComponent
return this.manyToOne return this.manyToOne
? this.selectionModel.selectionSize() > 1 && ? this.selectionModel.selectionSize() > 1 &&
this.selectionModel.getExcludedItems().length == 0 this.selectionModel.getExcludedItems().length == 0
: !this.selectionModel.isNoneSelected() : true
} }
get name(): string { get name(): string {
@@ -545,6 +586,8 @@ export class FilterableDropdownComponent
this.selectionModel.reset() this.selectionModel.reset()
this.modelIsDirty = false this.modelIsDirty = false
} }
this.selectionModel.singleSelect =
this.editing && !this.selectionModel.manyToOne
this.opened.next(this) this.opened.next(this)
} else { } else {
if (this.creating) { if (this.creating) {

View File

@@ -30,25 +30,24 @@
[placeholder]="placeholder" [placeholder]="placeholder"
[notFoundText]="notFoundText" [notFoundText]="notFoundText"
[multiple]="true" [multiple]="true"
bindValue="id"
[compareWith]="compareDocuments" [compareWith]="compareDocuments"
[trackByFn]="trackByFn" [trackByFn]="trackByFn"
[minTermLength]="2" [minTermLength]="2"
[loading]="loading" [loading]="loading"
[typeahead]="documentsInput$" [typeahead]="documentsInput$"
(mousedown)="$event.stopImmediatePropagation()" (mousedown)="$event.stopImmediatePropagation()"
(change)="onChange(selectedDocuments)"> (change)="onChange(selectedDocumentIDs)">
<ng-template ng-label-tmp let-document="item"> <ng-template ng-label-tmp let-document="item">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
@if (!disabled) { @if (!disabled) {
<button class="btn p-0 lh-1" (click)="unselect(document)" title="Remove link" i18n-title><i-bs name="x"></i-bs></button> <button class="btn p-0 lh-1" (click)="unselect(document)" (mousedown)="$event.stopImmediatePropagation()" type="button" title="Remove link" i18n-title><i-bs name="x"></i-bs></button>
} }
@if (document.title) { @if (document.title) {
<a routerLink="/documents/{{document.id}}" class="badge bg-light text-primary" (mousedown)="$event.stopImmediatePropagation();" title="Open link" i18n-title> <a routerLink="/documents/{{document.id}}" class="badge bg-light text-primary" (mousedown)="$event.stopImmediatePropagation();" title="Open link" i18n-title>
<i-bs width="0.9em" height="0.9em" name="file-text"></i-bs>&nbsp;<span>{{document.title}}</span> <i-bs width="0.9em" height="0.9em" name="file-text"></i-bs>&nbsp;<span>{{document.title}}</span>
</a> </a>
} @else { } @else {
<span class="badge bg-light text-muted" (click)="unselect(document)" title="Remove link" i18n-title> <span class="badge bg-light text-muted" (click)="unselect(document)" (mousedown)="$event.stopImmediatePropagation()" type="button" title="Remove link" i18n-title>
<i-bs width="0.9em" height="0.9em" name="exclamation-triangle-fill"></i-bs>&nbsp;<span i18n>Not found</span> <i-bs width="0.9em" height="0.9em" name="exclamation-triangle-fill"></i-bs>&nbsp;<span i18n>Not found</span>
</span> </span>
} }

View File

@@ -74,6 +74,11 @@ describe('DocumentLinkComponent', () => {
expect(component.selectedDocuments).toEqual([documents[1], documents[0]]) expect(component.selectedDocuments).toEqual([documents[1], documents[0]])
}) })
it('should retrieve document IDs from selected documents', () => {
component.selectedDocuments = documents
expect(component.selectedDocumentIDs).toEqual([1, 12, 16, 23])
})
it('should search API on select text input', () => { it('should search API on select text input', () => {
const listSpy = jest.spyOn(documentService, 'listFiltered') const listSpy = jest.spyOn(documentService, 'listFiltered')
listSpy.mockImplementation( listSpy.mockImplementation(

View File

@@ -71,6 +71,10 @@ export class DocumentLinkComponent
@Input() @Input()
placeholder: string = $localize`Search for documents` placeholder: string = $localize`Search for documents`
get selectedDocumentIDs(): number[] {
return this.selectedDocuments.map((d) => d.id)
}
constructor(private documentsService: DocumentService) { constructor(private documentsService: DocumentService) {
super() super()
} }
@@ -90,8 +94,8 @@ export class DocumentLinkComponent
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((documentResults) => { .subscribe((documentResults) => {
this.loading = false this.loading = false
this.selectedDocuments = documentIDs.map((id) => this.selectedDocuments = documentIDs.map(
documentResults.results.find((d) => d.id === id) (id) => documentResults.results.find((d) => d.id === id) ?? {}
) )
super.writeValue(documentIDs) super.writeValue(documentIDs)
}) })

View File

@@ -17,7 +17,7 @@
(change)="onChange(value)"> (change)="onChange(value)">
<ng-template ng-label-tmp let-item="item"> <ng-template ng-label-tmp let-item="item">
<button class="tag-wrap btn p-0 d-flex align-items-center" (click)="removeTag($event, item.id)" title="Remove tag" i18n-title> <button class="tag-wrap btn p-0 d-flex align-items-center" (click)="removeTag(item.id)" (mousedown)="$event.stopImmediatePropagation()" type="button" title="Remove tag" i18n-title>
<i-bs name="x" style="margin-inline-end: 1px;"></i-bs> <i-bs name="x" style="margin-inline-end: 1px;"></i-bs>
@if (item.id && tags) { @if (item.id && tags) {
<pngx-tag style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag> <pngx-tag style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag>
@@ -33,7 +33,7 @@
</ng-template> </ng-template>
</ng-select> </ng-select>
@if (allowCreate && !hideAddButton) { @if (allowCreate && !hideAddButton) {
<button class="btn btn-outline-secondary" type="button" (click)="createTag()" [disabled]="disabled"> <button class="btn btn-outline-secondary" type="button" (click)="createTag(null, true)" [disabled]="disabled">
<i-bs width="1.2em" height="1.2em" name="plus"></i-bs> <i-bs width="1.2em" height="1.2em" name="plus"></i-bs>
</button> </button>
} }

View File

@@ -154,11 +154,11 @@ describe('TagsComponent', () => {
it('support remove tags', () => { it('support remove tags', () => {
component.tags = tags component.tags = tags
component.value = [1, 2] component.value = [1, 2]
component.removeTag(new PointerEvent('point'), 2) component.removeTag(2)
expect(component.value).toEqual([1]) expect(component.value).toEqual([1])
component.disabled = true component.disabled = true
component.removeTag(new PointerEvent('point'), 1) component.removeTag(1)
expect(component.value).toEqual([1]) expect(component.value).toEqual([1])
}) })

View File

@@ -118,13 +118,10 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
} }
} }
removeTag(event: PointerEvent, id: number) { removeTag(tagID: number) {
if (this.disabled) return if (this.disabled) return
// prevent opening dropdown let index = this.value.indexOf(tagID)
event.stopImmediatePropagation()
let index = this.value.indexOf(id)
if (index > -1) { if (index > -1) {
let oldValue = this.value let oldValue = this.value
oldValue.splice(index, 1) oldValue.splice(index, 1)
@@ -133,7 +130,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
} }
} }
createTag(name: string = null) { createTag(name: string = null, add: boolean = false) {
var modal = this.modalService.open(TagEditDialogComponent, { var modal = this.modalService.open(TagEditDialogComponent, {
backdrop: 'static', backdrop: 'static',
}) })
@@ -146,9 +143,10 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
return firstValueFrom( return firstValueFrom(
(modal.componentInstance as TagEditDialogComponent).succeeded.pipe( (modal.componentInstance as TagEditDialogComponent).succeeded.pipe(
first(), first(),
tap(() => { tap((newTag) => {
this.tagService.listAll().subscribe((tags) => { this.tagService.listAll().subscribe((tags) => {
this.tags = tags.results this.tags = tags.results
add && this.addTag(newTag.id)
}) })
}) })
) )

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 } 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,12 @@ import { ToastService } from 'src/app/services/toast.service'
NgxBootstrapIconsModule, NgxBootstrapIconsModule,
], ],
}) })
export class SystemStatusDialogComponent { export class SystemStatusDialogComponent implements OnInit {
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
@@ -55,6 +58,17 @@ export class SystemStatusDialogComponent {
private permissionsService: PermissionsService private permissionsService: PermissionsService
) {} ) {}
public ngOnInit() {
this.versionMismatch =
environment.production &&
this.status.pngx_version &&
this.frontendVersion &&
this.status.pngx_version !== this.frontendVersion
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

@@ -43,7 +43,7 @@
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a> <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a>
} }
@case (DisplayField.CREATED) { @case (DisplayField.CREATED) {
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created_date | customDate}}</a> <a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created | customDate}}</a>
} }
@case (DisplayField.TITLE) { @case (DisplayField.TITLE) {
<a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a> <a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>

View File

@@ -82,10 +82,20 @@ describe('UploadFileWidgetComponent', () => {
}) })
it('should upload files', () => { it('should upload files', () => {
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles') const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
fixture.debugElement const file = new File(
.query(By.css('input')) [new Blob(['testing'], { type: 'application/pdf' })],
.nativeElement.dispatchEvent(new Event('change')) 'file.pdf'
)
const fileInput = fixture.debugElement.query(By.css('input'))
jest.spyOn(fileInput.nativeElement, 'files', 'get').mockReturnValue({
item: () => file,
length: 1,
[Symbol.iterator]: () => ({
next: () => ({ done: false, value: file }),
}),
} as any)
fileInput.nativeElement.dispatchEvent(new Event('change'))
expect(uploadSpy).toHaveBeenCalled() expect(uploadSpy).toHaveBeenCalled()
}) })

View File

@@ -134,9 +134,11 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
} }
public onFileSelected(event: Event) { public onFileSelected(event: Event) {
this.uploadDocumentsService.uploadFiles( const files = (event.target as HTMLInputElement).files
(event.target as HTMLInputElement).files for (let i = 0; i < files?.length; i++) {
) const file = files.item(i)
file && this.uploadDocumentsService.uploadFile(file)
}
} }
get slimSidebarEnabled(): boolean { get slimSidebarEnabled(): boolean {

View File

@@ -9,9 +9,9 @@
} }
<div class="input-group input-group-sm me-md-5 d-none d-md-flex"> <div class="input-group input-group-sm me-md-5 d-none d-md-flex">
<button class="btn btn-outline-secondary" (click)="decreaseZoom()" i18n>-</button> <button class="btn btn-outline-secondary" (click)="decreaseZoom()" i18n>-</button>
<select class="form-select" (change)="setZoom($event.target.value)"> <select class="form-select" (change)="setZoom($event.target.value)" [ngModel]="currentZoom">
@for (setting of zoomSettings; track setting) { @for (setting of zoomSettings; track setting) {
<option [value]="setting" [attr.selected]="isZoomSelected(setting) ? 'selected' : null"> <option [value]="setting">
{{ getZoomSettingTitle(setting) }} {{ getZoomSettingTitle(setting) }}
</option> </option>
} }
@@ -129,8 +129,8 @@
<div> <div>
<pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text> <pngx-input-text #inputTitle i18n-title title="Title" formControlName="title" [horizontal]="true" (keyup)="titleKeyUp($event)" [error]="error?.title"></pngx-input-text>
<pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number> <pngx-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" [horizontal]="true" formControlName='archive_serial_number'></pngx-input-number>
<pngx-input-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)" <pngx-input-date i18n-title title="Date created" formControlName="created" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
[error]="error?.created_date"></pngx-input-date> [error]="error?.created"></pngx-input-date>
<pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.Correspondent)" <pngx-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.Correspondent)"
(createNew)="createCorrespondent($event)" [hideAddButton]="createDisabled(DataType.Correspondent)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select> (createNew)="createCorrespondent($event)" [hideAddButton]="createDisabled(DataType.Correspondent)" [suggestions]="suggestions?.correspondents" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }"></pngx-input-select>
<pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.DocumentType)" <pngx-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event, DataType.DocumentType)"

View File

@@ -1,5 +1,10 @@
import { DatePipe } from '@angular/common' import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http' import {
HttpHeaders,
HttpResponse,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http'
import { import {
HttpTestingController, HttpTestingController,
provideHttpClientTesting, provideHttpClientTesting,
@@ -451,11 +456,11 @@ describe('DocumentDetailComponent', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showInfo') const toastSpy = jest.spyOn(toastService, 'showInfo')
updateSpy.mockImplementation((o) => of(doc)) patchSpy.mockImplementation((o) => of(doc))
component.save(true) component.save(true)
expect(updateSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).toHaveBeenCalled() expect(closeSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(toastSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.' 'Document "Doc 3" saved successfully.'
@@ -466,11 +471,11 @@ describe('DocumentDetailComponent', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showInfo') const toastSpy = jest.spyOn(toastService, 'showInfo')
updateSpy.mockImplementation((o) => of(doc)) patchSpy.mockImplementation((o) => of(doc))
component.save() component.save()
expect(updateSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(toastSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.' 'Document "Doc 3" saved successfully.'
@@ -482,12 +487,12 @@ describe('DocumentDetailComponent', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showError') const toastSpy = jest.spyOn(toastService, 'showError')
const error = new Error('failed to save') const error = new Error('failed to save')
updateSpy.mockImplementation(() => throwError(() => error)) patchSpy.mockImplementation(() => throwError(() => error))
component.save() component.save()
expect(updateSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(toastSpy).toHaveBeenCalledWith(
'Error saving document "Doc 3"', 'Error saving document "Doc 3"',
@@ -500,13 +505,13 @@ describe('DocumentDetailComponent', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showInfo') const toastSpy = jest.spyOn(toastService, 'showInfo')
updateSpy.mockImplementation(() => patchSpy.mockImplementation(() =>
throwError(() => new Error('failed to save')) throwError(() => new Error('failed to save'))
) )
component.save(true) component.save(true)
expect(updateSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).toHaveBeenCalled() expect(closeSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith( expect(toastSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.' 'Document "Doc 3" saved successfully.'
@@ -517,8 +522,8 @@ describe('DocumentDetailComponent', () => {
initNormally() initNormally()
const nextDocId = 100 const nextDocId = 100
component.title = 'Foo Bar' component.title = 'Foo Bar'
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
updateSpy.mockReturnValue(of(doc)) patchSpy.mockReturnValue(of(doc))
const nextSpy = jest.spyOn(documentListViewService, 'getNext') const nextSpy = jest.spyOn(documentListViewService, 'getNext')
nextSpy.mockReturnValue(of(nextDocId)) nextSpy.mockReturnValue(of(nextDocId))
const closeSpy = jest.spyOn(openDocumentsService, 'closeDocument') const closeSpy = jest.spyOn(openDocumentsService, 'closeDocument')
@@ -526,7 +531,7 @@ describe('DocumentDetailComponent', () => {
const navigateSpy = jest.spyOn(router, 'navigate') const navigateSpy = jest.spyOn(router, 'navigate')
component.saveEditNext() component.saveEditNext()
expect(updateSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(navigateSpy).toHaveBeenCalledWith(['documents', nextDocId]) expect(navigateSpy).toHaveBeenCalledWith(['documents', nextDocId])
expect expect
}) })
@@ -536,12 +541,12 @@ describe('DocumentDetailComponent', () => {
initNormally() initNormally()
component.title = 'Foo Bar' component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close') const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showError') const toastSpy = jest.spyOn(toastService, 'showError')
const error = new Error('failed to save') const error = new Error('failed to save')
updateSpy.mockImplementation(() => throwError(() => error)) patchSpy.mockImplementation(() => throwError(() => error))
component.saveEditNext() component.saveEditNext()
expect(updateSpy).toHaveBeenCalled() expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled() expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith('Error saving document', error) expect(toastSpy).toHaveBeenCalledWith('Error saving document', error)
}) })
@@ -786,14 +791,9 @@ describe('DocumentDetailComponent', () => {
it('should select correct zoom setting in dropdown', () => { it('should select correct zoom setting in dropdown', () => {
initNormally() initNormally()
component.setZoom(ZoomSetting.PageFit) component.setZoom(ZoomSetting.PageFit)
expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeTruthy() expect(component.currentZoom).toEqual(ZoomSetting.PageFit)
expect(component.isZoomSelected(ZoomSetting.One)).toBeFalsy()
component.setZoom(ZoomSetting.PageWidth)
expect(component.isZoomSelected(ZoomSetting.One)).toBeTruthy()
expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeFalsy()
component.setZoom(ZoomSetting.Quarter) component.setZoom(ZoomSetting.Quarter)
expect(component.isZoomSelected(ZoomSetting.Quarter)).toBeTruthy() expect(component.currentZoom).toEqual(ZoomSetting.Quarter)
expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeFalsy()
}) })
it('should support updating notes dynamically', () => { it('should support updating notes dynamically', () => {
@@ -965,10 +965,10 @@ describe('DocumentDetailComponent', () => {
expect(fixture.debugElement.nativeElement.textContent).toContain( expect(fixture.debugElement.nativeElement.textContent).toContain(
customFields[1].name customFields[1].name
) )
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
component.save(true) component.save(true)
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(2) expect(patchSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
expect(updateSpy.mock.lastCall[0].custom_fields[1]).toEqual({ expect(patchSpy.mock.lastCall[0].custom_fields[1]).toEqual({
field: customFields[1].id, field: customFields[1].id,
value: null, value: null,
}) })
@@ -985,13 +985,51 @@ describe('DocumentDetailComponent', () => {
expect( expect(
fixture.debugElement.query(By.css('form')).nativeElement.textContent fixture.debugElement.query(By.css('form')).nativeElement.textContent
).not.toContain('Field 1') ).not.toContain('Field 1')
const updateSpy = jest.spyOn(documentService, 'update') const patchSpy = jest.spyOn(documentService, 'patch')
component.save(true) component.save(true)
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength( expect(patchSpy.mock.lastCall[0].custom_fields).toHaveLength(
initialLength - 1 initialLength - 1
) )
}) })
it('should correctly determine changed fields', () => {
initNormally()
expect(component['getChangedFields']()).toEqual({
id: doc.id,
})
component.documentForm.get('title').setValue('Foo Bar')
component.documentForm.get('permissions_form').setValue({
owner: 1,
set_permissions: {
view: {
users: [2],
groups: [],
},
change: {
users: [3],
groups: [],
},
},
})
component.documentForm.get('title').markAsDirty()
component.documentForm.get('permissions_form').markAsDirty()
expect(component['getChangedFields']()).toEqual({
id: doc.id,
title: 'Foo Bar',
owner: 1,
set_permissions: {
view: {
users: [2],
groups: [],
},
change: {
users: [3],
groups: [],
},
},
})
})
it('should show custom field errors', () => { it('should show custom field errors', () => {
initNormally() initNormally()
component.error = { component.error = {
@@ -1331,6 +1369,34 @@ describe('DocumentDetailComponent', () => {
expect(urlRevokeSpy).toHaveBeenCalled() expect(urlRevokeSpy).toHaveBeenCalled()
}) })
it('should download a file with the correct filename', () => {
const mockBlob = new Blob(['test content'], { type: 'text/plain' })
const mockResponse = new HttpResponse({
body: mockBlob,
headers: new HttpHeaders({
'Content-Disposition': 'attachment; filename="test-file.txt"',
}),
})
const downloadUrl = 'http://example.com/download'
component.documentId = 123
jest.spyOn(documentService, 'getDownloadUrl').mockReturnValue(downloadUrl)
const createSpy = jest.spyOn(document, 'createElement')
const anchor: HTMLAnchorElement = {} as HTMLAnchorElement
createSpy.mockReturnValueOnce(anchor)
component.download(false)
httpTestingController
.expectOne(downloadUrl)
.flush(mockBlob, { headers: mockResponse.headers })
expect(createSpy).toHaveBeenCalledWith('a')
expect(anchor.download).toBe('test-file.txt')
createSpy.mockClear()
})
it('should get email enabled status from settings', () => { it('should get email enabled status from settings', () => {
jest.spyOn(settingsService, 'get').mockReturnValue(true) jest.spyOn(settingsService, 'get').mockReturnValue(true)
expect(component.emailEnabled).toBeTruthy() expect(component.emailEnabled).toBeTruthy()

View File

@@ -1,5 +1,5 @@
import { AsyncPipe, NgTemplateOutlet } from '@angular/common' import { AsyncPipe, NgTemplateOutlet } from '@angular/common'
import { HttpClient } from '@angular/common/http' import { HttpClient, HttpResponse } from '@angular/common/http'
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
import { import {
FormArray, FormArray,
@@ -77,6 +77,7 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.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 { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { getFilenameFromContentDisposition } from 'src/app/utils/http'
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter' import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
import * as UTIF from 'utif' import * as UTIF from 'utif'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
@@ -207,7 +208,7 @@ export class DocumentDetailComponent
documentForm: FormGroup = new FormGroup({ documentForm: FormGroup = new FormGroup({
title: new FormControl(''), title: new FormControl(''),
content: new FormControl(''), content: new FormControl(''),
created_date: new FormControl(), created: new FormControl(),
correspondent: new FormControl(), correspondent: new FormControl(),
document_type: new FormControl(), document_type: new FormControl(),
storage_path: new FormControl(), storage_path: new FormControl(),
@@ -489,7 +490,7 @@ export class DocumentDetailComponent
this.store = new BehaviorSubject({ this.store = new BehaviorSubject({
title: doc.title, title: doc.title,
content: doc.content, content: doc.content,
created_date: doc.created_date, created: doc.created,
correspondent: doc.correspondent, correspondent: doc.correspondent,
document_type: doc.document_type, document_type: doc.document_type,
storage_path: doc.storage_path, storage_path: doc.storage_path,
@@ -697,6 +698,7 @@ export class DocumentDetailComponent
.subscribe(({ newDocumentType, documentTypes }) => { .subscribe(({ newDocumentType, documentTypes }) => {
this.documentTypes = documentTypes.results this.documentTypes = documentTypes.results
this.documentForm.get('document_type').setValue(newDocumentType.id) this.documentForm.get('document_type').setValue(newDocumentType.id)
this.documentForm.get('document_type').markAsDirty()
}) })
} }
@@ -720,6 +722,7 @@ export class DocumentDetailComponent
.subscribe(({ newCorrespondent, correspondents }) => { .subscribe(({ newCorrespondent, correspondents }) => {
this.correspondents = correspondents.results this.correspondents = correspondents.results
this.documentForm.get('correspondent').setValue(newCorrespondent.id) this.documentForm.get('correspondent').setValue(newCorrespondent.id)
this.documentForm.get('correspondent').markAsDirty()
}) })
} }
@@ -741,6 +744,7 @@ export class DocumentDetailComponent
.subscribe(({ newStoragePath, storagePaths }) => { .subscribe(({ newStoragePath, storagePaths }) => {
this.storagePaths = storagePaths.results this.storagePaths = storagePaths.results
this.documentForm.get('storage_path').setValue(newStoragePath.id) this.documentForm.get('storage_path').setValue(newStoragePath.id)
this.documentForm.get('storage_path').markAsDirty()
}) })
} }
@@ -783,6 +787,7 @@ export class DocumentDetailComponent
this.title = doc.title this.title = doc.title
this.updateFormForCustomFields() this.updateFormForCustomFields()
this.documentForm.patchValue(doc) this.documentForm.patchValue(doc)
this.documentForm.markAsPristine()
this.openDocumentService.setDirty(doc, false) this.openDocumentService.setDirty(doc, false)
}, },
error: () => { error: () => {
@@ -793,11 +798,30 @@ export class DocumentDetailComponent
}) })
} }
private getChangedFields(): any {
const changes = {
id: this.document.id,
}
Object.keys(this.documentForm.controls).forEach((key) => {
if (this.documentForm.get(key).dirty) {
if (key === 'permissions_form') {
changes['owner'] =
this.documentForm.get('permissions_form').value['owner']
changes['set_permissions'] =
this.documentForm.get('permissions_form').value['set_permissions']
} else {
changes[key] = this.documentForm.get(key).value
}
}
})
return changes
}
save(close: boolean = false) { save(close: boolean = false) {
this.networkActive = true this.networkActive = true
;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change')) ;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change'))
this.documentsService this.documentsService
.update(this.document) .patch(this.getChangedFields())
.pipe(first()) .pipe(first())
.subscribe({ .subscribe({
next: (docValues) => { next: (docValues) => {
@@ -824,11 +848,18 @@ export class DocumentDetailComponent
}, },
error: (error) => { error: (error) => {
this.networkActive = false this.networkActive = false
if (!this.userCanEdit) { const canEdit =
this.permissionsService.currentUserHasObjectPermissions(
PermissionAction.Change,
this.document
)
if (!canEdit) {
// document was 'given away'
this.openDocumentService.setDirty(this.document, false)
this.toastService.showInfo( this.toastService.showInfo(
$localize`Document "${this.document.title}" saved successfully.` $localize`Document "${this.document.title}" saved successfully.`
) )
close && this.close() this.close()
} else { } else {
this.error = error.error this.error = error.error
this.toastService.showError( this.toastService.showError(
@@ -844,7 +875,7 @@ export class DocumentDetailComponent
this.networkActive = true this.networkActive = true
this.store.next(this.documentForm.value) this.store.next(this.documentForm.value)
this.documentsService this.documentsService
.update(this.document) .patch(this.getChangedFields())
.pipe( .pipe(
switchMap((updateResult) => { switchMap((updateResult) => {
return this.documentListViewService return this.documentListViewService
@@ -988,44 +1019,46 @@ export class DocumentDetailComponent
this.documentId, this.documentId,
original original
) )
this.http.get(downloadUrl, { responseType: 'blob' }).subscribe({ this.http
next: (blob) => { .get(downloadUrl, { observe: 'response', responseType: 'blob' })
this.downloading = false .subscribe({
const blobParts = [blob] next: (response: HttpResponse<Blob>) => {
const file = new File( const contentDisposition = response.headers.get('Content-Disposition')
blobParts, const filename =
original getFilenameFromContentDisposition(contentDisposition) ||
? this.document.original_file_name this.document.title
: this.document.archived_file_name, const blob = new Blob([response.body], {
{ type: response.body.type,
type: original ? this.document.mime_type : 'application/pdf',
}
)
if (
!this.deviceDetectorService.isDesktop() &&
navigator.canShare &&
navigator.canShare({ files: [file] })
) {
navigator.share({
files: [file],
}) })
} else { this.downloading = false
const url = URL.createObjectURL(blob) const file = new File([blob], filename, {
const a = document.createElement('a') type: response.body.type,
a.href = url })
a.download = this.document.title if (
a.click() !this.deviceDetectorService.isDesktop() &&
URL.revokeObjectURL(url) navigator.canShare &&
} navigator.canShare({ files: [file] })
}, ) {
error: (error) => { navigator.share({
this.downloading = false files: [file],
this.toastService.showError( })
$localize`Error downloading document`, } else {
error const url = URL.createObjectURL(blob)
) const a = document.createElement('a')
}, a.href = url
}) a.download = filename
a.click()
URL.revokeObjectURL(url)
}
},
error: (error) => {
this.downloading = false
this.toastService.showError(
$localize`Error downloading document`,
error
)
},
})
} }
hasNext() { hasNext() {
@@ -1089,12 +1122,10 @@ export class DocumentDetailComponent
) )
} }
isZoomSelected(setting: ZoomSetting): boolean { get currentZoom() {
if (this.previewZoomScale === ZoomSetting.PageFit) { if (this.previewZoomScale === ZoomSetting.PageFit) {
return setting === ZoomSetting.PageFit return ZoomSetting.PageFit
} } else return this.previewZoomSetting
return this.previewZoomSetting === setting
} }
getZoomSettingTitle(setting: ZoomSetting): string { getZoomSettingTitle(setting: ZoomSetting): string {
@@ -1279,9 +1310,7 @@ export class DocumentDetailComponent
this.document.custom_fields?.forEach((fieldInstance) => { this.document.custom_fields?.forEach((fieldInstance) => {
this.customFieldFormFields.push( this.customFieldFormFields.push(
new FormGroup({ new FormGroup({
field: new FormControl( field: new FormControl(fieldInstance.field),
this.getCustomFieldFromInstance(fieldInstance)?.id
),
value: new FormControl(fieldInstance.value), value: new FormControl(fieldInstance.value),
}), }),
{ emitEvent } { emitEvent }
@@ -1297,6 +1326,8 @@ export class DocumentDetailComponent
created: new Date(), created: new Date(),
}) })
this.updateFormForCustomFields(true) this.updateFormForCustomFields(true)
this.documentForm.get('custom_fields').markAsDirty()
this.documentForm.updateValueAndValidity()
} }
public removeField(fieldInstance: CustomFieldInstance) { public removeField(fieldInstance: CustomFieldInstance) {
@@ -1305,6 +1336,7 @@ export class DocumentDetailComponent
1 1
) )
this.updateFormForCustomFields(true) this.updateFormForCustomFields(true)
this.documentForm.get('custom_fields').markAsDirty()
this.documentForm.updateValueAndValidity() this.documentForm.updateValueAndValidity()
} }

View File

@@ -20,10 +20,8 @@
@if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Tag)) { @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Tag)) {
<pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title <pngx-filterable-dropdown title="Tags" icon="tag-fill" i18n-title
filterPlaceholder="Filter tags" i18n-filterPlaceholder filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags"
[disabled]="!userCanEditAll || disabled" [disabled]="!userCanEditAll || disabled"
[editing]="true" [editing]="true"
[manyToOne]="true"
[applyOnClose]="applyOnClose" [applyOnClose]="applyOnClose"
[createRef]="createTag.bind(this)" [createRef]="createTag.bind(this)"
(opened)="openTagsDropdown()" (opened)="openTagsDropdown()"
@@ -36,7 +34,6 @@
@if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) { @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
<pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title <pngx-filterable-dropdown title="Correspondent" icon="person-fill" i18n-title
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[items]="correspondents"
[disabled]="!userCanEditAll || disabled" [disabled]="!userCanEditAll || disabled"
[editing]="true" [editing]="true"
[applyOnClose]="applyOnClose" [applyOnClose]="applyOnClose"
@@ -51,7 +48,6 @@
@if (permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) { @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
<pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title <pngx-filterable-dropdown title="Document type" icon="file-earmark-fill" i18n-title
filterPlaceholder="Filter document types" i18n-filterPlaceholder filterPlaceholder="Filter document types" i18n-filterPlaceholder
[items]="documentTypes"
[disabled]="!userCanEditAll || disabled" [disabled]="!userCanEditAll || disabled"
[editing]="true" [editing]="true"
[applyOnClose]="applyOnClose" [applyOnClose]="applyOnClose"
@@ -66,7 +62,6 @@
@if (permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) { @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
<pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title <pngx-filterable-dropdown title="Storage path" icon="folder-fill" i18n-title
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
[items]="storagePaths"
[disabled]="!userCanEditAll || disabled" [disabled]="!userCanEditAll || disabled"
[editing]="true" [editing]="true"
[applyOnClose]="applyOnClose" [applyOnClose]="applyOnClose"
@@ -81,10 +76,8 @@
@if (permissionService.currentUserCan(PermissionAction.View, PermissionType.CustomField)) { @if (permissionService.currentUserCan(PermissionAction.View, PermissionType.CustomField)) {
<pngx-filterable-dropdown title="Custom fields" icon="ui-radios" i18n-title <pngx-filterable-dropdown title="Custom fields" icon="ui-radios" i18n-title
filterPlaceholder="Filter custom fields" i18n-filterPlaceholder filterPlaceholder="Filter custom fields" i18n-filterPlaceholder
[items]="customFields"
[disabled]="!userCanEditAll" [disabled]="!userCanEditAll"
[editing]="true" [editing]="true"
[manyToOne]="true"
[applyOnClose]="applyOnClose" [applyOnClose]="applyOnClose"
[createRef]="createCustomField.bind(this)" [createRef]="createCustomField.bind(this)"
(opened)="openCustomFieldsDropdown()" (opened)="openCustomFieldsDropdown()"

View File

@@ -1150,10 +1150,10 @@ describe('BulkEditorComponent', () => {
it('should not attempt to retrieve objects if user does not have permissions', () => { it('should not attempt to retrieve objects if user does not have permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true) jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.tags).toBeUndefined() expect(component.tagSelectionModel.items.length).toEqual(0)
expect(component.correspondents).toBeUndefined() expect(component.correspondentSelectionModel.items.length).toEqual(0)
expect(component.documentTypes).toBeUndefined() expect(component.documentTypeSelectionModel.items.length).toEqual(0)
expect(component.storagePaths).toBeUndefined() expect(component.storagePathsSelectionModel.items.length).toEqual(0)
httpTestingController.expectNone(`${environment.apiBaseUrl}documents/tags/`) httpTestingController.expectNone(`${environment.apiBaseUrl}documents/tags/`)
httpTestingController.expectNone( httpTestingController.expectNone(
`${environment.apiBaseUrl}documents/correspondents/` `${environment.apiBaseUrl}documents/correspondents/`
@@ -1204,7 +1204,9 @@ describe('BulkEditorComponent', () => {
expect(tagListAllSpy).toHaveBeenCalled() expect(tagListAllSpy).toHaveBeenCalled()
expect(tagSelectionModelToggleSpy).toHaveBeenCalledWith(newTag.id) expect(tagSelectionModelToggleSpy).toHaveBeenCalledWith(newTag.id)
expect(component.tags).toEqual(tags.results) expect(component.tagSelectionModel.items).toEqual(
[{ id: null, name: 'Not assigned' }].concat(tags.results as any)
)
}) })
it('should support create new correspondent', () => { it('should support create new correspondent', () => {
@@ -1251,7 +1253,9 @@ describe('BulkEditorComponent', () => {
expect(correspondentSelectionModelToggleSpy).toHaveBeenCalledWith( expect(correspondentSelectionModelToggleSpy).toHaveBeenCalledWith(
newCorrespondent.id newCorrespondent.id
) )
expect(component.correspondents).toEqual(correspondents.results) expect(component.correspondentSelectionModel.items).toEqual(
[{ id: null, name: 'Not assigned' }].concat(correspondents.results as any)
)
}) })
it('should support create new document type', () => { it('should support create new document type', () => {
@@ -1295,7 +1299,9 @@ describe('BulkEditorComponent', () => {
expect(documentTypeSelectionModelToggleSpy).toHaveBeenCalledWith( expect(documentTypeSelectionModelToggleSpy).toHaveBeenCalledWith(
newDocumentType.id newDocumentType.id
) )
expect(component.documentTypes).toEqual(documentTypes.results) expect(component.documentTypeSelectionModel.items).toEqual(
[{ id: null, name: 'Not assigned' }].concat(documentTypes.results as any)
)
}) })
it('should support create new storage path', () => { it('should support create new storage path', () => {
@@ -1339,7 +1345,9 @@ describe('BulkEditorComponent', () => {
expect(storagePathsSelectionModelToggleSpy).toHaveBeenCalledWith( expect(storagePathsSelectionModelToggleSpy).toHaveBeenCalledWith(
newStoragePath.id newStoragePath.id
) )
expect(component.storagePaths).toEqual(storagePaths.results) expect(component.storagePathsSelectionModel.items).toEqual(
[{ id: null, name: 'Not assigned' }].concat(storagePaths.results as any)
)
}) })
it('should support create new custom field', () => { it('should support create new custom field', () => {
@@ -1391,7 +1399,9 @@ describe('BulkEditorComponent', () => {
expect(customFieldsSelectionModelToggleSpy).toHaveBeenCalledWith( expect(customFieldsSelectionModelToggleSpy).toHaveBeenCalledWith(
newCustomField.id newCustomField.id
) )
expect(component.customFields).toEqual(customFields.results) expect(component.customFieldsSelectionModel.items).toEqual(
[{ id: null, name: 'Not assigned' }].concat(customFields.results as any)
)
}) })
it('should open the bulk edit custom field values dialog with correct parameters', () => { it('should open the bulk edit custom field values dialog with correct parameters', () => {
@@ -1416,17 +1426,17 @@ describe('BulkEditorComponent', () => {
const toastServiceShowErrorSpy = jest.spyOn(toastService, 'showError') const toastServiceShowErrorSpy = jest.spyOn(toastService, 'showError')
const listReloadSpy = jest.spyOn(documentListViewService, 'reload') const listReloadSpy = jest.spyOn(documentListViewService, 'reload')
component.customFields = [ component.customFieldsSelectionModel.items = [
{ id: 1, name: 'Custom Field 1', data_type: CustomFieldDataType.String }, { id: 1, name: 'Custom Field 1', data_type: CustomFieldDataType.String },
{ id: 2, name: 'Custom Field 2', data_type: CustomFieldDataType.String }, { id: 2, name: 'Custom Field 2', data_type: CustomFieldDataType.String },
] ] as any
component.setCustomFieldValues({ component.setCustomFieldValues({
itemsToAdd: [{ id: 1 }, { id: 2 }], itemsToAdd: [{ id: 1 }, { id: 2 }],
itemsToRemove: [1], itemsToRemove: [1],
} as any) } as any)
expect(modal.componentInstance.customFields).toEqual(component.customFields) expect(modal.componentInstance.customFields.length).toEqual(2)
expect(modal.componentInstance.fieldsToAddIds).toEqual([1, 2]) expect(modal.componentInstance.fieldsToAddIds).toEqual([1, 2])
expect(modal.componentInstance.documents).toEqual([3, 4]) expect(modal.componentInstance.documents).toEqual([3, 4])

View File

@@ -14,12 +14,8 @@ import { saveAs } from 'file-saver'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first, map, Subject, switchMap, takeUntil } from 'rxjs' import { first, map, Subject, switchMap, takeUntil } from 'rxjs'
import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component' import { ConfirmDialogComponent } from 'src/app/components/common/confirm-dialog/confirm-dialog.component'
import { Correspondent } from 'src/app/data/correspondent'
import { CustomField } from 'src/app/data/custom-field' import { CustomField } from 'src/app/data/custom-field'
import { DocumentType } from 'src/app/data/document-type'
import { MatchingModel } from 'src/app/data/matching-model' import { MatchingModel } from 'src/app/data/matching-model'
import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings' import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service' import { DocumentListViewService } from 'src/app/services/document-list-view.service'
@@ -75,17 +71,11 @@ export class BulkEditorComponent
extends ComponentWithPermissions extends ComponentWithPermissions
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
tags: Tag[] tagSelectionModel = new FilterableDropdownSelectionModel(true)
correspondents: Correspondent[]
documentTypes: DocumentType[]
storagePaths: StoragePath[]
customFields: CustomField[]
tagSelectionModel = new FilterableDropdownSelectionModel()
correspondentSelectionModel = new FilterableDropdownSelectionModel() correspondentSelectionModel = new FilterableDropdownSelectionModel()
documentTypeSelectionModel = new FilterableDropdownSelectionModel() documentTypeSelectionModel = new FilterableDropdownSelectionModel()
storagePathsSelectionModel = new FilterableDropdownSelectionModel() storagePathsSelectionModel = new FilterableDropdownSelectionModel()
customFieldsSelectionModel = new FilterableDropdownSelectionModel() customFieldsSelectionModel = new FilterableDropdownSelectionModel(true)
tagDocumentCounts: SelectionDataItem[] tagDocumentCounts: SelectionDataItem[]
correspondentDocumentCounts: SelectionDataItem[] correspondentDocumentCounts: SelectionDataItem[]
documentTypeDocumentCounts: SelectionDataItem[] documentTypeDocumentCounts: SelectionDataItem[]
@@ -176,7 +166,7 @@ export class BulkEditorComponent
this.tagService this.tagService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.tags = result.results)) .subscribe((result) => (this.tagSelectionModel.items = result.results))
} }
if ( if (
this.permissionService.currentUserCan( this.permissionService.currentUserCan(
@@ -187,7 +177,9 @@ export class BulkEditorComponent
this.correspondentService this.correspondentService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.correspondents = result.results)) .subscribe(
(result) => (this.correspondentSelectionModel.items = result.results)
)
} }
if ( if (
this.permissionService.currentUserCan( this.permissionService.currentUserCan(
@@ -198,7 +190,9 @@ export class BulkEditorComponent
this.documentTypeService this.documentTypeService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.documentTypes = result.results)) .subscribe(
(result) => (this.documentTypeSelectionModel.items = result.results)
)
} }
if ( if (
this.permissionService.currentUserCan( this.permissionService.currentUserCan(
@@ -209,7 +203,9 @@ export class BulkEditorComponent
this.storagePathService this.storagePathService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.storagePaths = result.results)) .subscribe(
(result) => (this.storagePathsSelectionModel.items = result.results)
)
} }
if ( if (
this.permissionService.currentUserCan( this.permissionService.currentUserCan(
@@ -220,7 +216,9 @@ export class BulkEditorComponent
this.customFieldService this.customFieldService
.listAll() .listAll()
.pipe(first()) .pipe(first())
.subscribe((result) => (this.customFields = result.results)) .subscribe(
(result) => (this.customFieldsSelectionModel.items = result.results)
)
} }
this.downloadForm this.downloadForm
@@ -651,7 +649,7 @@ export class BulkEditorComponent
) )
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newTag, tags }) => { .subscribe(({ newTag, tags }) => {
this.tags = tags.results this.tagSelectionModel.items = tags.results
this.tagSelectionModel.toggle(newTag.id) this.tagSelectionModel.toggle(newTag.id)
}) })
} }
@@ -674,7 +672,7 @@ export class BulkEditorComponent
) )
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newCorrespondent, correspondents }) => { .subscribe(({ newCorrespondent, correspondents }) => {
this.correspondents = correspondents.results this.correspondentSelectionModel.items = correspondents.results
this.correspondentSelectionModel.toggle(newCorrespondent.id) this.correspondentSelectionModel.toggle(newCorrespondent.id)
}) })
} }
@@ -695,7 +693,7 @@ export class BulkEditorComponent
) )
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newDocumentType, documentTypes }) => { .subscribe(({ newDocumentType, documentTypes }) => {
this.documentTypes = documentTypes.results this.documentTypeSelectionModel.items = documentTypes.results
this.documentTypeSelectionModel.toggle(newDocumentType.id) this.documentTypeSelectionModel.toggle(newDocumentType.id)
}) })
} }
@@ -716,7 +714,7 @@ export class BulkEditorComponent
) )
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newStoragePath, storagePaths }) => { .subscribe(({ newStoragePath, storagePaths }) => {
this.storagePaths = storagePaths.results this.storagePathsSelectionModel.items = storagePaths.results
this.storagePathsSelectionModel.toggle(newStoragePath.id) this.storagePathsSelectionModel.toggle(newStoragePath.id)
}) })
} }
@@ -737,7 +735,7 @@ export class BulkEditorComponent
) )
.pipe(takeUntil(this.unsubscribeNotifier)) .pipe(takeUntil(this.unsubscribeNotifier))
.subscribe(({ newCustomField, customFields }) => { .subscribe(({ newCustomField, customFields }) => {
this.customFields = customFields.results this.customFieldsSelectionModel.items = customFields.results
this.customFieldsSelectionModel.toggle(newCustomField.id) this.customFieldsSelectionModel.toggle(newCustomField.id)
}) })
} }
@@ -875,7 +873,9 @@ export class BulkEditorComponent
}) })
const dialog = const dialog =
modal.componentInstance as CustomFieldsBulkEditDialogComponent modal.componentInstance as CustomFieldsBulkEditDialogComponent
dialog.customFields = this.customFields dialog.customFields = (
this.customFieldsSelectionModel.items as CustomField[]
).filter((f) => f.id !== null)
dialog.fieldsToAddIds = changedCustomFields.itemsToAdd.map( dialog.fieldsToAddIds = changedCustomFields.itemsToAdd.map(
(item) => item.id (item) => item.id
) )

View File

@@ -15,7 +15,7 @@
} }
</div> </div>
<div class="col"> <div class="col col-md-10">
<div class="card-body"> <div class="card-body">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h5 class="card-title w-100"> <h5 class="card-title w-100">
@@ -112,14 +112,14 @@
@if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) { @if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
<ng-template #dateTooltip> <ng-template #dateTooltip>
<div class="d-flex flex-column text-light"> <div class="d-flex flex-column text-light">
<span i18n>Created: {{ document.created_date | customDate }}</span> <span i18n>Created: {{ document.created | customDate }}</span>
<span i18n>Added: {{ document.added | customDate }}</span> <span i18n>Added: {{ document.added | customDate }}</span>
<span i18n>Modified: {{ document.modified | customDate }}</span> <span i18n>Modified: {{ document.modified | customDate }}</span>
</div> </div>
</ng-template> </ng-template>
@if (displayFields.includes(DisplayField.CREATED)) { @if (displayFields.includes(DisplayField.CREATED)) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip"> <div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small> <i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created | customDate:'mediumDate'}}</small>
</div> </div>
} }
@if (displayFields.includes(DisplayField.ADDED)) { @if (displayFields.includes(DisplayField.ADDED)) {

View File

@@ -73,14 +73,14 @@
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between"> <div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
<ng-template #dateTooltip> <ng-template #dateTooltip>
<div class="d-flex flex-column text-light"> <div class="d-flex flex-column text-light">
<span i18n>Created: {{ document.created_date | customDate }}</span> <span i18n>Created: {{ document.created | customDate }}</span>
<span i18n>Added: {{ document.added | customDate }}</span> <span i18n>Added: {{ document.added | customDate }}</span>
<span i18n>Modified: {{ document.modified | customDate }}</span> <span i18n>Modified: {{ document.modified | customDate }}</span>
</div> </div>
</ng-template> </ng-template>
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip"> <div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs> <i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
<small>{{document.created_date | customDate:'mediumDate'}}</small> <small>{{document.created | customDate:'mediumDate'}}</small>
</div> </div>
</div> </div>
} }

View File

@@ -348,7 +348,7 @@
} }
@if (activeDisplayFields.includes(DisplayField.CREATED)) { @if (activeDisplayFields.includes(DisplayField.CREATED)) {
<td> <td>
{{d.created_date | customDate}} {{d.created | customDate}}
</td> </td>
} }
@if (activeDisplayFields.includes(DisplayField.ADDED)) { @if (activeDisplayFields.includes(DisplayField.ADDED)) {

View File

@@ -35,11 +35,9 @@
<div class="col-auto"> <div class="col-auto">
<div class="d-flex flex-wrap gap-3"> <div class="d-flex flex-wrap gap-3">
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag) && tags.length > 0) { @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Tag) && tagSelectionModel.items.length > 0) {
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Tags" icon="tag-fill" i18n-title <pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Tags" icon="tag-fill" i18n-title
filterPlaceholder="Filter tags" i18n-filterPlaceholder filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags"
[manyToOne]="true"
[(selectionModel)]="tagSelectionModel" [(selectionModel)]="tagSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onTagsDropdownOpen()" (opened)="onTagsDropdownOpen()"
@@ -48,10 +46,9 @@
[disabled]="disabled" [disabled]="disabled"
shortcutKey="t"></pngx-filterable-dropdown> shortcutKey="t"></pngx-filterable-dropdown>
} }
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent) && correspondents.length > 0) { @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.Correspondent) && correspondentSelectionModel.items.length > 0) {
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Correspondent" icon="person-fill" i18n-title <pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Correspondent" icon="person-fill" i18n-title
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[items]="correspondents"
[(selectionModel)]="correspondentSelectionModel" [(selectionModel)]="correspondentSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onCorrespondentDropdownOpen()" (opened)="onCorrespondentDropdownOpen()"
@@ -60,10 +57,9 @@
[disabled]="disabled" [disabled]="disabled"
shortcutKey="y"></pngx-filterable-dropdown> shortcutKey="y"></pngx-filterable-dropdown>
} }
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.DocumentType) && documentTypes.length > 0) { @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.DocumentType) && documentTypeSelectionModel.items.length > 0) {
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Document type" icon="file-earmark-fill" i18n-title <pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Document type" icon="file-earmark-fill" i18n-title
filterPlaceholder="Filter document types" i18n-filterPlaceholder filterPlaceholder="Filter document types" i18n-filterPlaceholder
[items]="documentTypes"
[(selectionModel)]="documentTypeSelectionModel" [(selectionModel)]="documentTypeSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onDocumentTypeDropdownOpen()" (opened)="onDocumentTypeDropdownOpen()"
@@ -72,10 +68,9 @@
[disabled]="disabled" [disabled]="disabled"
shortcutKey="u"></pngx-filterable-dropdown> shortcutKey="u"></pngx-filterable-dropdown>
} }
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.StoragePath) && storagePaths.length > 0) { @if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.StoragePath) && storagePathSelectionModel.items.length > 0) {
<pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Storage path" icon="folder-fill" i18n-title <pngx-filterable-dropdown class="flex-fill fade" [class.show]="show" title="Storage path" icon="folder-fill" i18n-title
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
[items]="storagePaths"
[(selectionModel)]="storagePathSelectionModel" [(selectionModel)]="storagePathSelectionModel"
(selectionModelChange)="updateRules()" (selectionModelChange)="updateRules()"
(opened)="onStoragePathDropdownOpen()" (opened)="onStoragePathDropdownOpen()"

View File

@@ -69,6 +69,7 @@ import {
FILTER_STORAGE_PATH, FILTER_STORAGE_PATH,
FILTER_TITLE, FILTER_TITLE,
FILTER_TITLE_CONTENT, FILTER_TITLE_CONTENT,
NEGATIVE_NULL_FILTER_VALUE,
} from 'src/app/data/filter-rule-type' } from 'src/app/data/filter-rule-type'
import { StoragePath } from 'src/app/data/storage-path' import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag' import { Tag } from 'src/app/data/tag'
@@ -671,9 +672,6 @@ describe('FilterEditorComponent', () => {
value: '12', value: '12',
}, },
] ]
expect(component.correspondentSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.correspondentSelectionModel.intersection).toEqual( expect(component.correspondentSelectionModel.intersection).toEqual(
Intersection.Include Intersection.Include
) )
@@ -681,6 +679,19 @@ describe('FilterEditorComponent', () => {
correspondents[0], correspondents[0],
]) ])
component.toggleCorrespondent(12) // coverage component.toggleCorrespondent(12) // coverage
component.filterRules = [
{
rule_type: FILTER_CORRESPONDENT,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
},
]
expect(component.correspondentSelectionModel.intersection).toEqual(
Intersection.Exclude
)
expect(component.correspondentSelectionModel.getExcludedItems()).toEqual([
{ id: NEGATIVE_NULL_FILTER_VALUE, name: 'Not assigned' },
])
})) }))
it('should ingest filter rules for has any of correspondents', fakeAsync(() => { it('should ingest filter rules for has any of correspondents', fakeAsync(() => {
@@ -754,9 +765,6 @@ describe('FilterEditorComponent', () => {
value: '22', value: '22',
}, },
] ]
expect(component.documentTypeSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.documentTypeSelectionModel.intersection).toEqual( expect(component.documentTypeSelectionModel.intersection).toEqual(
Intersection.Include Intersection.Include
) )
@@ -764,6 +772,19 @@ describe('FilterEditorComponent', () => {
document_types[0], document_types[0],
]) ])
component.toggleDocumentType(22) // coverage component.toggleDocumentType(22) // coverage
component.filterRules = [
{
rule_type: FILTER_DOCUMENT_TYPE,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
},
]
expect(component.documentTypeSelectionModel.intersection).toEqual(
Intersection.Exclude
)
expect(component.documentTypeSelectionModel.getExcludedItems()).toEqual([
{ id: NEGATIVE_NULL_FILTER_VALUE, name: 'Not assigned' },
])
})) }))
it('should ingest filter rules for has any of document types', fakeAsync(() => { it('should ingest filter rules for has any of document types', fakeAsync(() => {
@@ -780,9 +801,6 @@ describe('FilterEditorComponent', () => {
value: '23', value: '23',
}, },
] ]
expect(component.documentTypeSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.documentTypeSelectionModel.intersection).toEqual( expect(component.documentTypeSelectionModel.intersection).toEqual(
Intersection.Include Intersection.Include
) )
@@ -837,9 +855,6 @@ describe('FilterEditorComponent', () => {
value: '32', value: '32',
}, },
] ]
expect(component.storagePathSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.storagePathSelectionModel.intersection).toEqual( expect(component.storagePathSelectionModel.intersection).toEqual(
Intersection.Include Intersection.Include
) )
@@ -847,6 +862,19 @@ describe('FilterEditorComponent', () => {
storage_paths[0], storage_paths[0],
]) ])
component.toggleStoragePath(32) // coverage component.toggleStoragePath(32) // coverage
component.filterRules = [
{
rule_type: FILTER_STORAGE_PATH,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
},
]
expect(component.storagePathSelectionModel.intersection).toEqual(
Intersection.Exclude
)
expect(component.storagePathSelectionModel.getExcludedItems()).toEqual([
{ id: NEGATIVE_NULL_FILTER_VALUE, name: 'Not assigned' },
])
})) }))
it('should ingest filter rules for has any of storage paths', fakeAsync(() => { it('should ingest filter rules for has any of storage paths', fakeAsync(() => {
@@ -1398,6 +1426,19 @@ describe('FilterEditorComponent', () => {
value: null, value: null,
}, },
]) ])
const excludeButton = correspondentsFilterableDropdown.queryAll(
By.css('input[value=exclude]')
)[0]
excludeButton.nativeElement.checked = true
excludeButton.triggerEventHandler('change')
fixture.detectChanges()
expect(component.filterRules).toEqual([
{
rule_type: FILTER_CORRESPONDENT,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
},
])
})) }))
it('should convert user input to correct filter rules on document type selections', fakeAsync(() => { it('should convert user input to correct filter rules on document type selections', fakeAsync(() => {
@@ -1455,6 +1496,19 @@ describe('FilterEditorComponent', () => {
value: null, value: null,
}, },
]) ])
const excludeButton = docTypesFilterableDropdown.queryAll(
By.css('input[value=exclude]')
)[0]
excludeButton.nativeElement.checked = true
excludeButton.triggerEventHandler('change')
fixture.detectChanges()
expect(component.filterRules).toEqual([
{
rule_type: FILTER_DOCUMENT_TYPE,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
},
])
})) }))
it('should convert user input to correct filter rules on storage path selections', fakeAsync(() => { it('should convert user input to correct filter rules on storage path selections', fakeAsync(() => {
@@ -1512,6 +1566,19 @@ describe('FilterEditorComponent', () => {
value: null, value: null,
}, },
]) ])
const excludeButton = storagePathsFilterableDropdown.queryAll(
By.css('input[value=exclude]')
)[0]
excludeButton.nativeElement.checked = true
excludeButton.triggerEventHandler('change')
fixture.detectChanges()
expect(component.filterRules).toEqual([
{
rule_type: FILTER_STORAGE_PATH,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
},
])
})) }))
it('should convert user input to correct filter rules on custom field selections', fakeAsync(() => { it('should convert user input to correct filter rules on custom field selections', fakeAsync(() => {

View File

@@ -26,14 +26,12 @@ import {
switchMap, switchMap,
takeUntil, takeUntil,
} from 'rxjs/operators' } from 'rxjs/operators'
import { Correspondent } from 'src/app/data/correspondent'
import { CustomField } from 'src/app/data/custom-field' import { CustomField } from 'src/app/data/custom-field'
import { import {
CustomFieldQueryLogicalOperator, CustomFieldQueryLogicalOperator,
CustomFieldQueryOperator, CustomFieldQueryOperator,
} from 'src/app/data/custom-field-query' } from 'src/app/data/custom-field-query'
import { Document } from 'src/app/data/document' import { Document } from 'src/app/data/document'
import { DocumentType } from 'src/app/data/document-type'
import { FilterRule } from 'src/app/data/filter-rule' import { FilterRule } from 'src/app/data/filter-rule'
import { import {
FILTER_ADDED_AFTER, FILTER_ADDED_AFTER,
@@ -75,9 +73,8 @@ import {
FILTER_STORAGE_PATH, FILTER_STORAGE_PATH,
FILTER_TITLE, FILTER_TITLE,
FILTER_TITLE_CONTENT, FILTER_TITLE_CONTENT,
NEGATIVE_NULL_FILTER_VALUE,
} from 'src/app/data/filter-rule-type' } from 'src/app/data/filter-rule-type'
import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag'
import { import {
PermissionAction, PermissionAction,
PermissionType, PermissionType,
@@ -251,7 +248,9 @@ export class FilterEditorComponent
case FILTER_HAS_CORRESPONDENT_ANY: case FILTER_HAS_CORRESPONDENT_ANY:
if (rule.value) { if (rule.value) {
return $localize`Correspondent: ${ return $localize`Correspondent: ${
this.correspondents.find((c) => c.id == +rule.value)?.name this.correspondentSelectionModel.items.find(
(c) => c.id == +rule.value
)?.name
}` }`
} else { } else {
return $localize`Without correspondent` return $localize`Without correspondent`
@@ -261,7 +260,9 @@ export class FilterEditorComponent
case FILTER_HAS_DOCUMENT_TYPE_ANY: case FILTER_HAS_DOCUMENT_TYPE_ANY:
if (rule.value) { if (rule.value) {
return $localize`Document type: ${ return $localize`Document type: ${
this.documentTypes.find((dt) => dt.id == +rule.value)?.name this.documentTypeSelectionModel.items.find(
(dt) => dt.id == +rule.value
)?.name
}` }`
} else { } else {
return $localize`Without document type` return $localize`Without document type`
@@ -271,7 +272,9 @@ export class FilterEditorComponent
case FILTER_HAS_STORAGE_PATH_ANY: case FILTER_HAS_STORAGE_PATH_ANY:
if (rule.value) { if (rule.value) {
return $localize`Storage path: ${ return $localize`Storage path: ${
this.storagePaths.find((sp) => sp.id == +rule.value)?.name this.storagePathSelectionModel.items.find(
(sp) => sp.id == +rule.value
)?.name
}` }`
} else { } else {
return $localize`Without storage path` return $localize`Without storage path`
@@ -279,7 +282,7 @@ export class FilterEditorComponent
case FILTER_HAS_TAGS_ALL: case FILTER_HAS_TAGS_ALL:
return $localize`Tag: ${ return $localize`Tag: ${
this.tags.find((t) => t.id == +rule.value)?.name this.tagSelectionModel.items.find((t) => t.id == +rule.value)?.name
}` }`
case FILTER_HAS_ANY_TAG: case FILTER_HAS_ANY_TAG:
@@ -326,10 +329,6 @@ export class FilterEditorComponent
@ViewChild('textFilterInput') @ViewChild('textFilterInput')
textFilterInput: ElementRef textFilterInput: ElementRef
tags: Tag[] = []
correspondents: Correspondent[] = []
documentTypes: DocumentType[] = []
storagePaths: StoragePath[] = []
customFields: CustomField[] = [] customFields: CustomField[] = []
tagDocumentCounts: SelectionDataItem[] tagDocumentCounts: SelectionDataItem[]
@@ -370,7 +369,7 @@ export class FilterEditorComponent
) )
} }
tagSelectionModel = new FilterableDropdownSelectionModel() tagSelectionModel = new FilterableDropdownSelectionModel(true)
correspondentSelectionModel = new FilterableDropdownSelectionModel() correspondentSelectionModel = new FilterableDropdownSelectionModel()
documentTypeSelectionModel = new FilterableDropdownSelectionModel() documentTypeSelectionModel = new FilterableDropdownSelectionModel()
storagePathSelectionModel = new FilterableDropdownSelectionModel() storagePathSelectionModel = new FilterableDropdownSelectionModel()
@@ -551,6 +550,19 @@ export class FilterEditorComponent
) )
break break
case FILTER_CORRESPONDENT: case FILTER_CORRESPONDENT:
this.correspondentSelectionModel.intersection =
rule.value == NEGATIVE_NULL_FILTER_VALUE.toString()
? Intersection.Exclude
: Intersection.Include
this.correspondentSelectionModel.set(
rule.value ? +rule.value : null,
this.correspondentSelectionModel.intersection ==
Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded,
false
)
break
case FILTER_HAS_CORRESPONDENT_ANY: case FILTER_HAS_CORRESPONDENT_ANY:
this.correspondentSelectionModel.logicalOperator = LogicalOperator.Or this.correspondentSelectionModel.logicalOperator = LogicalOperator.Or
this.correspondentSelectionModel.intersection = Intersection.Include this.correspondentSelectionModel.intersection = Intersection.Include
@@ -569,6 +581,18 @@ export class FilterEditorComponent
) )
break break
case FILTER_DOCUMENT_TYPE: case FILTER_DOCUMENT_TYPE:
this.documentTypeSelectionModel.intersection =
rule.value == NEGATIVE_NULL_FILTER_VALUE.toString()
? Intersection.Exclude
: Intersection.Include
this.documentTypeSelectionModel.set(
rule.value ? +rule.value : null,
this.documentTypeSelectionModel.intersection == Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded,
false
)
break
case FILTER_HAS_DOCUMENT_TYPE_ANY: case FILTER_HAS_DOCUMENT_TYPE_ANY:
this.documentTypeSelectionModel.logicalOperator = LogicalOperator.Or this.documentTypeSelectionModel.logicalOperator = LogicalOperator.Or
this.documentTypeSelectionModel.intersection = Intersection.Include this.documentTypeSelectionModel.intersection = Intersection.Include
@@ -587,6 +611,18 @@ export class FilterEditorComponent
) )
break break
case FILTER_STORAGE_PATH: case FILTER_STORAGE_PATH:
this.storagePathSelectionModel.intersection =
rule.value == NEGATIVE_NULL_FILTER_VALUE.toString()
? Intersection.Exclude
: Intersection.Include
this.storagePathSelectionModel.set(
rule.value ? +rule.value : null,
this.storagePathSelectionModel.intersection == Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded,
false
)
break
case FILTER_HAS_STORAGE_PATH_ANY: case FILTER_HAS_STORAGE_PATH_ANY:
this.storagePathSelectionModel.logicalOperator = LogicalOperator.Or this.storagePathSelectionModel.logicalOperator = LogicalOperator.Or
this.storagePathSelectionModel.intersection = Intersection.Include this.storagePathSelectionModel.intersection = Intersection.Include
@@ -809,9 +845,21 @@ export class FilterEditorComponent
}) })
}) })
} }
if (this.correspondentSelectionModel.isNoneSelected()) { if (
this.correspondentSelectionModel.isNoneSelected() &&
this.correspondentSelectionModel.intersection == Intersection.Include
) {
filterRules.push({ rule_type: FILTER_CORRESPONDENT, value: null }) filterRules.push({ rule_type: FILTER_CORRESPONDENT, value: null })
} else { } else {
if (
this.correspondentSelectionModel.isNoneSelected() &&
this.correspondentSelectionModel.intersection == Intersection.Exclude
) {
filterRules.push({
rule_type: FILTER_CORRESPONDENT,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
})
}
this.correspondentSelectionModel this.correspondentSelectionModel
.getSelectedItems() .getSelectedItems()
.forEach((correspondent) => { .forEach((correspondent) => {
@@ -822,6 +870,7 @@ export class FilterEditorComponent
}) })
this.correspondentSelectionModel this.correspondentSelectionModel
.getExcludedItems() .getExcludedItems()
.filter((correspondent) => correspondent.id > 0)
.forEach((correspondent) => { .forEach((correspondent) => {
filterRules.push({ filterRules.push({
rule_type: FILTER_DOES_NOT_HAVE_CORRESPONDENT, rule_type: FILTER_DOES_NOT_HAVE_CORRESPONDENT,
@@ -829,9 +878,21 @@ export class FilterEditorComponent
}) })
}) })
} }
if (this.documentTypeSelectionModel.isNoneSelected()) { if (
this.documentTypeSelectionModel.isNoneSelected() &&
this.documentTypeSelectionModel.intersection === Intersection.Include
) {
filterRules.push({ rule_type: FILTER_DOCUMENT_TYPE, value: null }) filterRules.push({ rule_type: FILTER_DOCUMENT_TYPE, value: null })
} else { } else {
if (
this.documentTypeSelectionModel.isNoneSelected() &&
this.documentTypeSelectionModel.intersection == Intersection.Exclude
) {
filterRules.push({
rule_type: FILTER_DOCUMENT_TYPE,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
})
}
this.documentTypeSelectionModel this.documentTypeSelectionModel
.getSelectedItems() .getSelectedItems()
.forEach((documentType) => { .forEach((documentType) => {
@@ -842,6 +903,7 @@ export class FilterEditorComponent
}) })
this.documentTypeSelectionModel this.documentTypeSelectionModel
.getExcludedItems() .getExcludedItems()
.filter((documentType) => documentType.id > 0)
.forEach((documentType) => { .forEach((documentType) => {
filterRules.push({ filterRules.push({
rule_type: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE, rule_type: FILTER_DOES_NOT_HAVE_DOCUMENT_TYPE,
@@ -849,9 +911,21 @@ export class FilterEditorComponent
}) })
}) })
} }
if (this.storagePathSelectionModel.isNoneSelected()) { if (
this.storagePathSelectionModel.isNoneSelected() &&
this.storagePathSelectionModel.intersection == Intersection.Include
) {
filterRules.push({ rule_type: FILTER_STORAGE_PATH, value: null }) filterRules.push({ rule_type: FILTER_STORAGE_PATH, value: null })
} else { } else {
if (
this.storagePathSelectionModel.isNoneSelected() &&
this.storagePathSelectionModel.intersection == Intersection.Exclude
) {
filterRules.push({
rule_type: FILTER_STORAGE_PATH,
value: NEGATIVE_NULL_FILTER_VALUE.toString(),
})
}
this.storagePathSelectionModel this.storagePathSelectionModel
.getSelectedItems() .getSelectedItems()
.forEach((storagePath) => { .forEach((storagePath) => {
@@ -862,6 +936,7 @@ export class FilterEditorComponent
}) })
this.storagePathSelectionModel this.storagePathSelectionModel
.getExcludedItems() .getExcludedItems()
.filter((storagePath) => storagePath.id > 0)
.forEach((storagePath) => { .forEach((storagePath) => {
filterRules.push({ filterRules.push({
rule_type: FILTER_DOES_NOT_HAVE_STORAGE_PATH, rule_type: FILTER_DOES_NOT_HAVE_STORAGE_PATH,
@@ -1062,7 +1137,7 @@ export class FilterEditorComponent
) { ) {
this.loadingCountTotal++ this.loadingCountTotal++
this.tagService.listAll().subscribe((result) => { this.tagService.listAll().subscribe((result) => {
this.tags = result.results this.tagSelectionModel.items = result.results
this.maybeCompleteLoading() this.maybeCompleteLoading()
}) })
} }
@@ -1074,7 +1149,7 @@ export class FilterEditorComponent
) { ) {
this.loadingCountTotal++ this.loadingCountTotal++
this.correspondentService.listAll().subscribe((result) => { this.correspondentService.listAll().subscribe((result) => {
this.correspondents = result.results this.correspondentSelectionModel.items = result.results
this.maybeCompleteLoading() this.maybeCompleteLoading()
}) })
} }
@@ -1086,7 +1161,7 @@ export class FilterEditorComponent
) { ) {
this.loadingCountTotal++ this.loadingCountTotal++
this.documentTypeService.listAll().subscribe((result) => { this.documentTypeService.listAll().subscribe((result) => {
this.documentTypes = result.results this.documentTypeSelectionModel.items = result.results
this.maybeCompleteLoading() this.maybeCompleteLoading()
}) })
} }
@@ -1098,7 +1173,7 @@ export class FilterEditorComponent
) { ) {
this.loadingCountTotal++ this.loadingCountTotal++
this.storagePathService.listAll().subscribe((result) => { this.storagePathService.listAll().subscribe((result) => {
this.storagePaths = result.results this.storagePathSelectionModel.items = result.results
this.maybeCompleteLoading() this.maybeCompleteLoading()
}) })
} }

View File

@@ -2,13 +2,6 @@
<ng-content select="[content]"></ng-content> <ng-content select="[content]"></ng-content>
</div> </div>
<div class="global-dropzone-overlay position-fixed top-0 start-0 bottom-0 end-0 text-center pe-none fade" [class.show]="fileIsOver" [class.hide]="hidden"> <div class="global-dropzone-overlay position-fixed top-0 start-0 bottom-0 end-0 text-center pe-none" [class.active]="fileIsOver && !hidden">
<h2 class="pe-none position-absolute top-50 start-50 translate-middle" i18n>Drop files to begin upload</h2> <h2 class="pe-none position-absolute top-50 start-50 translate-middle" i18n>Drop files to begin upload</h2>
</div> </div>
<ngx-file-drop
dropZoneClassName="visually-hidden"
contentClassName="visually-hidden"
(onFileDrop)="dropped($event)"
#ngxFileDrop>
</ngx-file-drop>

View File

@@ -1,8 +1,14 @@
.global-dropzone-overlay { .global-dropzone-overlay {
opacity: 0;
transition: opacity 0.25s ease-in-out;
background-color: hsla(var(--pngx-primary), var(--pngx-primary-lightness), .8); background-color: hsla(var(--pngx-primary), var(--pngx-primary-lightness), .8);
z-index: 1200; z-index: 1200;
h2 { h2 {
color: var(--pngx-primary-text-contrast) color: var(--pngx-primary-text-contrast)
} }
&.active {
opacity: 1;
}
} }

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