Compare commits

...

167 Commits

Author SHA1 Message Date
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 1fe166e014ebde9a6fb6861819efa7e705175cd6.
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
252 changed files with 42711 additions and 32974 deletions

View File

@ -21,19 +21,17 @@
# This file is intended only to be used through VSCOde devcontainers. See README.md
# in the folder .devcontainer.
services:
broker:
image: docker.io/library/redis:7
restart: unless-stopped
volumes:
- ./redisdata:/data
# No ports need to be exposed; the VSCode DevContainer plugin manages them.
paperless-development:
image: paperless-ngx
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
restart: unless-stopped
depends_on:
@ -60,25 +58,20 @@ services:
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
PAPERLESS_STATICDIR: ./src/documents/static
PAPERLESS_DEBUG: true
# 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"
gotenberg:
image: docker.io/gotenberg/gotenberg:8.17
restart: unless-stopped
# The Gotenberg Chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even JavaScript.
command:
- "gotenberg"
- "--chromium-disable-javascript=true"
- "--chromium-allow-list=file:///tmp/.*"
tika:
image: docker.io/apache/tika:latest
restart: unless-stopped
volumes:
data:
media:

View File

@ -5,7 +5,6 @@ version: 2
# Required for uv support for now
enable-beta-ecosystems: true
updates:
# Enable version updates for pnpm
- package-ecosystem: "npm"
target-branch: "dev"
@ -35,7 +34,6 @@ updates:
patterns:
- "@typescript-eslint*"
- "eslint"
# Enable version updates for Python
- package-ecosystem: "uv"
target-branch: "dev"
@ -59,6 +57,7 @@ updates:
django:
patterns:
- "*django*"
- "drf-*"
major-versions:
update-types:
- "major"
@ -70,7 +69,6 @@ updates:
patterns:
- psycopg*
- zxing-cpp
# Enable updates for GitHub Actions
- package-ecosystem: "github-actions"
target-branch: "dev"
@ -90,7 +88,6 @@ updates:
- "major"
- "minor"
- "patch"
# Update Dockerfile in root directory
- package-ecosystem: "docker"
directory: "/"
@ -100,12 +97,10 @@ updates:
reviewers:
- "paperless-ngx/ci-cd"
labels:
- "ci-cd"
- "dependencies"
commit-message:
prefix: "docker"
include: "scope"
# Update Docker Compose files in docker/compose directory
- package-ecosystem: "docker-compose"
directory: "/docker/compose/"
@ -115,7 +110,6 @@ updates:
reviewers:
- "paperless-ngx/ci-cd"
labels:
- "ci-cd"
- "dependencies"
commit-message:
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:
- title: 'Breaking Changes'
labels:
@ -17,7 +5,7 @@ categories:
- title: 'Notable Changes'
labels:
- 'notable'
- title: 'Features'
- title: 'Features / Enhancements'
labels:
- 'enhancement'
- title: 'Bug Fixes'

View File

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

View File

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

View File

@ -10,16 +10,14 @@
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main, dev ]
branches: [main, dev]
pull_request:
# The branches below must be a subset of the branches above
branches: [ dev ]
branches: [dev]
schedule:
- cron: '28 13 * * 5'
jobs:
analyze:
name: Analyze
@ -28,27 +26,23 @@ jobs:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
language: ['javascript', 'python']
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# 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.
# 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
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# 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.
# 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
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3

View File

@ -1,35 +1,28 @@
name: Crowdin Action
on:
workflow_dispatch:
schedule:
- cron: '2 */12 * * *'
push:
paths: [
'src/locale/**',
'src-ui/messages.xlf',
'src-ui/src/locale/**'
]
branches: [ dev ]
paths: ['src/locale/**', 'src-ui/messages.xlf', 'src-ui/src/locale/**']
branches: [dev]
jobs:
synchronize-with-crowdin:
name: Crowdin Sync
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: crowdin action
uses: crowdin/github-action@v2
with:
upload_translations: false
download_translations: true
crowdin_branch_name: 'dev'
localization_branch_name: l10n_dev
pull_request_labels: 'skip-changelog, translation'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: crowdin action
uses: crowdin/github-action@v2
with:
upload_translations: false
download_translations: true
crowdin_branch_name: 'dev'
localization_branch_name: l10n_dev
pull_request_labels: 'skip-changelog, translation'
env:
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 {
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
on:
pull_request_target: #_target allows access to secrets
types:
@ -8,10 +7,8 @@ on:
branches:
- main
- dev
permissions:
contents: read
jobs:
pr_opened_or_reopened:
name: pr_opened_or_reopened

View File

@ -1,18 +1,14 @@
name: 'Repository Maintenance'
on:
schedule:
- cron: '0 3 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock
jobs:
stale:
name: 'Stale'
@ -27,9 +23,8 @@ jobs:
stale-issue-label: stale
stale-pr-label: stale
stale-issue-message: >
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.
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.
lock-threads:
name: 'Lock Old Threads'
if: github.repository_owner == 'paperless-ngx'
@ -42,20 +37,14 @@ jobs:
discussion-inactive-days: '30'
log-output: true
issue-comment: >
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.
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.
pr-comment: >
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.
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.
discussion-comment: >
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.
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.
close-answered-discussions:
name: 'Close Answered Discussions'
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"
hooks:
- 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 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:
- English name of the language (the localized name can be added on Crowdin).

View File

@ -32,7 +32,7 @@ RUN set -eux \
# Purpose: Installs s6-overlay and rootfs
# Comments:
# - 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.6.16-python3.12-bookworm-slim AS s6-overlay-base
WORKDIR /usr/src/s6
@ -47,7 +47,7 @@ ENV \
ARG TARGETARCH
ARG TARGETVARIANT
# 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 \
xz-utils"
@ -239,6 +239,7 @@ COPY --from=compile-frontend --chown=1000:1000 /src/src/documents/static/fronten
# add users, setup scripts
# Mount the compiled frontend to expected location
RUN set -eux \
&& sed -i '1s|^#!/usr/bin/env python3|#!/command/with-contenv python3|' manage.py \
&& echo "Setting up user/group" \
&& addgroup --gid 1000 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
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

View File

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

View File

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

View File

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

View File

@ -22,21 +22,16 @@
# - Upload 'docker-compose.env' by clicking on 'Load variables from .env file'
# - Modify the environment variables as needed
# - 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
# documentation.
services:
broker:
image: docker.io/library/redis:7
image: docker.io/library/redis:8
restart: unless-stopped
volumes:
- redisdata:/data
db:
image: docker.io/library/postgres:17
restart: unless-stopped
@ -46,7 +41,6 @@ services:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless
webserver:
image: ghcr.io/paperless-ngx/paperless-ngx:latest
restart: unless-stopped
@ -65,7 +59,6 @@ services:
PAPERLESS_DBHOST: db
env_file:
- stack.env
volumes:
data:
media:

View File

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

View File

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

View File

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

View File

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

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
if [[ "${FILENAME##*/}" == PAPERLESS_*_FILE ]]; then
# 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
fi
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
# Trim off trailing _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
cat "${SECRETFILE}" > "${FILESTRIP}"
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 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 \
"${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}" \
"${tmp_dir}"; do
if [[ ! -d "${dir}" ]]; then
mkdir --parents --verbose "${dir}"
fi
done
declare -r extra_dirs=(
"${main_dirs[@]}"
"${data_dir}/index"
"${media_root_dir}/documents"
"${media_root_dir}/documents/originals"
"${media_root_dir}/documents/thumbnails"
)
echo "${log_prefix} Adjusting file and folder permissions"
for dir in \
"${export_dir}" \
"${data_dir}" \
"${media_root_dir}" \
"${consume_dir}" \
"${tmp_dir}"; do
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown --changes paperless:paperless {} +
done
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
# Non-root mode: Create directories as current user, warn about permission issues
echo "${log_prefix} Running in non-root mode, checking directories"
current_uid=$(id --user)
current_gid=$(id --group)
for dir in "${extra_dirs[@]}"; do
if [[ ! -d "${dir}" ]]; then
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
# shellcheck shell=bash
declare -r log_prefix="[init-migrations]"
declare -r data_dir="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}"
(
# 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}"
echo "${log_prefix} Apply database migrations..."
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
exec python3 manage.py migrate --skip-checks --no-input
else
exec s6-setuidgid paperless python3 manage.py migrate --skip-checks --no-input
fi
cd "${PAPERLESS_SRC_DIR}"
) 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
# 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
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
printf "/usr/src/paperless" > /var/run/s6/container_environment/HOME
echo "${log_prefix} paperless-ngx docker container starting init as root"
fi

View File

@ -14,7 +14,7 @@ if [[ -n "${PAPERLESS_FORCE_SCRIPT_NAME}" ]]; then
fi
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
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

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

@ -565,19 +565,15 @@ document.
### Managing encryption {#encryption}
Documents can be stored in Paperless using GnuPG encryption.
!!! warning
Encryption is deprecated since [paperless-ng 0.9](changelog.md#paperless-ng-090) and doesn't really
provide any additional security, since you have to store the passphrase
in a configuration file on the same system as the encrypted documents
for paperless to work. Furthermore, the entire text content of the
documents is stored plain in the database, even if your documents are
encrypted. Filenames are not encrypted as well.
Also, the web server provides transparent access to your encrypted
documents.
Encryption was removed in [paperless-ng 0.9](changelog.md#paperless-ng-090)
because it did not really provide any additional security, the passphrase
was stored in a configuration file on the same system as the documents.
Furthermore, the entire text content of the documents is stored plain in
the database, even if your documents are encrypted. Filenames are not
encrypted as well. Finally, the web server provides transparent access to
your encrypted documents.
Consider running paperless on an encrypted filesystem instead, which
will then at least provide security against physical hardware theft.
@ -633,3 +629,11 @@ entries created prior to this are not removed. This command allows you to prune
```shell
prune_audit_logs
```
### Create superuser {#create-superuser}
If you need to create a superuser, use the following command:
```shell
createsuperuser
```

View File

@ -132,7 +132,7 @@ use cases:
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":
@ -270,7 +270,7 @@ The following methods are supported:
- `remove_tag`
- Requires `parameters`: `{ "tag": TAG_ID }`
- `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`
- No `parameters` required
- `reprocess`
@ -413,3 +413,14 @@ Initial API version.
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
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,423 @@
# Changelog
## 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
### Features

View File

@ -404,7 +404,7 @@ set this value to /paperless. No trailing slash!
#### [`PAPERLESS_STATIC_URL=<path>`](#PAPERLESS_STATIC_URL) {#PAPERLESS_STATIC_URL}
: 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.
Defaults to "/static/".
@ -629,7 +629,13 @@ If both the [PAPERLESS_ACCOUNT_DEFAULT_GROUPS](#PAPERLESS_ACCOUNT_DEFAULT_GROUPS
!!! 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}
@ -1057,9 +1063,9 @@ be used with caution!
## 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.
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
@ -1670,7 +1676,7 @@ started by the container.
## 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)
#### [`PAPERLESS_EMAIL_HOST=<str>`](#PAPERLESS_EMAIL_HOST) {#PAPERLESS_EMAIL_HOST}

View File

@ -84,7 +84,7 @@ first-time setup.
$ 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
# 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
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_?
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
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

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`
line to `image: paperlessngx/paperless-ngx:latest`.
6. To be able to login, you will need a "superuser". To create it,
execute the following command:
6. Run `docker compose up -d`. This will create and start the necessary containers.
```shell-session
docker compose run --rm webserver createsuperuser
```
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.
7. Congratulations! Your Paperless-ngx instance should now be accessible at `http://127.0.0.1:8000`
(or similar, depending on your configuration). When you first access the web interface, you will be
prompted to create a superuser account.
### 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>`
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
# This creates the database schema.
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
```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
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
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

View File

@ -292,7 +292,9 @@ many workers attempting to access the database simultaneously.
Consider changing to the PostgreSQL database if you will be processing
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
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"

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,
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
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
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
consumption directory will stored inside the Paperless-ngx file
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
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
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)
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 in Paperless-ngx are based around ['global' permissions](#global-permissions) as well as
@ -369,7 +395,7 @@ fields and permissions, which will be merged.
### Workflow Triggers
#### Types
#### Types {#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,
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
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 before the date, negative offsets will trigger after).
The following flow diagram illustrates the three document trigger types:
@ -429,11 +456,11 @@ Workflows allow you to filter by:
### Workflow Actions
#### Types
#### Types {#workflow-action-types}
The following workflow action types are available:
##### Assignment
##### Assignment {#workflow-action-assignment}
"Assignment" actions can assign:
@ -443,7 +470,7 @@ The following workflow action types are available:
- View and / or edit permissions to users or groups
- 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:
@ -452,7 +479,7 @@ The following workflow action types are available:
- View and / or edit permissions
- 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:
@ -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
- 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:
@ -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
- `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
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:
icon: material/brightness-auto
name: Switch to light mode
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
@ -60,17 +58,17 @@ markdown_extensions:
emoji_generator: !!python/name:material.extensions.emoji.to_svg
strict: true
nav:
- index.md
- setup.md
- 'Basic Usage': usage.md
- configuration.md
- administration.md
- advanced_usage.md
- 'REST API': api.md
- development.md
- 'FAQs': faq.md
- troubleshooting.md
- changelog.md
- index.md
- setup.md
- 'Basic Usage': usage.md
- configuration.md
- administration.md
- advanced_usage.md
- 'REST API': api.md
- development.md
- 'FAQs': faq.md
- troubleshooting.md
- changelog.md
copyright: Copyright &copy; 2016 - 2023 Daniel Quinn, Jonas Winkler, and the Paperless-ngx team
extra:
social:
@ -83,5 +81,5 @@ extra:
plugins:
- search
- glightbox:
skip_classes:
- no-lightbox
skip_classes:
- no-lightbox

View File

@ -1,6 +1,6 @@
[project]
name = "paperless-ngx"
version = "2.15.0"
version = "2.16.1"
description = "A community-supported supercharged version of paperless: scan, index and archive all your physical documents"
readme = "README.md"
requires-python = ">=3.10"
@ -16,20 +16,20 @@ classifiers = [
dependencies = [
"bleach~=6.2.0",
"celery[redis]~=5.4.0",
"celery[redis]~=5.5.1",
"channels~=4.2",
"channels-redis~=4.2",
"concurrent-log-handler~=0.9.25",
"dateparser~=1.2",
# WARNING: django does not use semver.
# 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-auditlog~=3.0.0",
"django-celery-results~=2.5.1",
"django-auditlog~=3.1.2",
"django-celery-results~=2.6.0",
"django-compression-middleware~=0.5.0",
"django-cors-headers~=4.7.0",
"django-extensions~=3.2.3",
"django-extensions~=4.1",
"django-filter~=25.1",
"django-guardian~=2.4.0",
"django-multiselectfield~=0.1.13",
@ -37,11 +37,11 @@ dependencies = [
"djangorestframework~=3.15",
"djangorestframework-guardian~=0.3.0",
"drf-spectacular~=0.28",
"drf-spectacular-sidecar~=2025.3.1",
"drf-spectacular-sidecar~=2025.4.1",
"drf-writable-nested~=0.7.1",
"filelock~=3.17.0",
"filelock~=3.18.0",
"flower~=2.0.1",
"gotenberg-client~=0.9.0",
"gotenberg-client~=0.10.0",
"httpx-oauth~=0.16",
"imap-tools~=1.10.0",
"inotifyrecursive~=0.3",
@ -52,12 +52,12 @@ dependencies = [
"pathvalidate~=3.2.3",
"pdf2image~=1.17.0",
"python-dateutil~=2.9.0",
"python-dotenv~=1.0.1",
"python-dotenv~=1.1.0",
"python-gnupg~=0.5.4",
"python-ipware~=3.0.0",
"python-magic~=0.4.27",
"pyzbar~=0.1.9",
"rapidfuzz~=3.12.1",
"rapidfuzz~=3.13.0",
"redis[hiredis]~=5.2.1",
"scikit-learn~=1.6.1",
"setproctitle~=1.3.4",
@ -65,7 +65,7 @@ dependencies = [
"tqdm~=4.67.1",
"watchdog~=6.0",
"whitenoise~=6.9",
"whoosh~=2.7",
"whoosh-reloaded>=2.7.5",
"zxing-cpp~=2.3.0",
]
@ -78,7 +78,7 @@ optional-dependencies.postgres = [
"psycopg-c==3.2.5",
]
optional-dependencies.webserver = [
"granian~=2.0.1",
"granian[uvloop]~=2.2.0",
]
[dependency-groups]
@ -227,27 +227,9 @@ lint.per-file-ignores."src/documents/tests/test_consumer.py" = [
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

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 {},
}

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +1,27 @@
{
"name": "paperless-ui",
"version": "0.0.0",
"name": "paperless-ngx-ui",
"version": "2.16.1",
"scripts": {
"preinstall": "npx only-allow pnpm",
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test --no-watch --coverage",
"lint": "ng lint",
"postinstall": "patch-package"
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/cdk": "^19.2.2",
"@angular/common": "~19.2.1",
"@angular/compiler": "~19.2.1",
"@angular/core": "~19.2.1",
"@angular/forms": "~19.2.1",
"@angular/localize": "~19.2.1",
"@angular/platform-browser": "~19.2.1",
"@angular/platform-browser-dynamic": "~19.2.1",
"@angular/router": "~19.2.1",
"@angular/cdk": "^19.2.14",
"@angular/common": "~19.2.9",
"@angular/compiler": "~19.2.9",
"@angular/core": "~19.2.9",
"@angular/forms": "~19.2.9",
"@angular/localize": "~19.2.9",
"@angular/platform-browser": "~19.2.9",
"@angular/platform-browser-dynamic": "~19.2.9",
"@angular/router": "~19.2.9",
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
"@ng-select/ng-select": "^14.2.3",
"@ng-select/ng-select": "^14.7.0",
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3",
@ -33,7 +32,6 @@
"ngx-color": "^10.0.0",
"ngx-cookie-service": "^19.1.2",
"ngx-device-detector": "^9.0.0",
"ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^16.0.0",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
@ -42,32 +40,31 @@
"zone.js": "^0.15.0"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^19.0.0",
"@angular-builders/jest": "^19.0.0",
"@angular-devkit/build-angular": "^19.2.1",
"@angular-devkit/core": "^19.2.1",
"@angular-devkit/schematics": "^19.2.1",
"@angular-eslint/builder": "19.2.1",
"@angular-eslint/eslint-plugin": "19.2.1",
"@angular-eslint/eslint-plugin-template": "19.2.1",
"@angular-eslint/schematics": "19.2.1",
"@angular-eslint/template-parser": "19.2.1",
"@angular/cli": "~19.2.1",
"@angular/compiler-cli": "~19.2.1",
"@angular-builders/custom-webpack": "^19.0.1",
"@angular-builders/jest": "^19.0.1",
"@angular-devkit/build-angular": "^19.2.10",
"@angular-devkit/core": "^19.2.10",
"@angular-devkit/schematics": "^19.2.10",
"@angular-eslint/builder": "19.3.0",
"@angular-eslint/eslint-plugin": "19.3.0",
"@angular-eslint/eslint-plugin-template": "19.3.0",
"@angular-eslint/schematics": "19.3.0",
"@angular-eslint/template-parser": "19.3.0",
"@angular/cli": "~19.2.10",
"@angular/compiler-cli": "~19.2.9",
"@codecov/webpack-plugin": "^1.9.0",
"@playwright/test": "^1.50.1",
"@playwright/test": "^1.51.1",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.9",
"@typescript-eslint/eslint-plugin": "^8.26.1",
"@typescript-eslint/parser": "^8.26.1",
"@typescript-eslint/utils": "^8.26.1",
"eslint": "^9.22.0",
"@types/node": "^22.15.3",
"@typescript-eslint/eslint-plugin": "^8.31.1",
"@typescript-eslint/parser": "^8.31.1",
"@typescript-eslint/utils": "^8.31.1",
"eslint": "^9.25.1",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-junit": "^16.0.0",
"jest-preset-angular": "^14.5.3",
"jest-preset-angular": "^14.5.5",
"jest-websocket-mock": "^2.5.0",
"patch-package": "^8.0.0",
"prettier-plugin-organize-imports": "^4.1.0",
"ts-node": "~10.9.1",
"typescript": "^5.5.4"

File diff suppressed because one or more lines are too long

3879
src-ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -121,19 +121,4 @@ HTMLCanvasElement.prototype.getContext = <
typeof HTMLCanvasElement.prototype.getContext
>jest.fn()
// pdfjs
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', () => ({}))
jest.mock('pdfjs-dist')

View File

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

View File

@ -105,9 +105,9 @@ describe('ConfigComponent', () => {
it('should support JSON validation for e.g. user_args', () => {
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" }' })
expect(component.errors).toEqual({ user_args: null })
expect(component.errors['user_args']).toBeNull()
})
it('should upload file, show error if necessary', () => {

View File

@ -15,7 +15,7 @@
</svg>
<div class="ms-2 ms-md-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled">
@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="byline text-uppercase font-monospace" i18n>by Paperless-ngx</span>
</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 {
// compensate for 2 buttons on the right
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-toggle:hover {
opacity: 0.7;

View File

@ -89,7 +89,7 @@
@if (searchResults?.documents.length) {
<h6 class="dropdown-header" i18n="@@searchResults.documents">Documents</h6>
@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) {

View File

@ -405,7 +405,7 @@ describe('GlobalSearchComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled()
// succeed
editDialog.succeeded.emit(true)
editDialog.succeeded.emit(object as any)
expect(toastInfoSpy).toHaveBeenCalled()
})
@ -456,7 +456,7 @@ describe('GlobalSearchComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled()
// succeed
editDialog.succeeded.emit(true)
editDialog.succeeded.emit(searchResults.tags[0] as any)
expect(toastInfoSpy).toHaveBeenCalled()
})

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>
<i-bs name="ui-radios"></i-bs>
<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'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.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 { 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 {
public popperOptions = pngxPopperOptions
@Input()
documentId: number

View File

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

View File

@ -123,7 +123,15 @@
<p class="small" i18n>Set scheduled trigger offset and which date field to use.</p>
<div class="row">
<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 the workflow before the date, negative values after."
i18n-hint
></pngx-input-number>
</div>
<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>

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdown, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
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 { ObjectWithPermissions } from 'src/app/data/object-with-permissions'
import { FilterPipe } from 'src/app/pipes/filter.pipe'
@ -61,15 +62,56 @@ export class FilterableDropdownSelectionModel {
}
set items(items: MatchingModel[]) {
this._items = items
this.sortItems()
if (items) {
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() {
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
} 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
} else if (
this.getNonTemporary(a.id) == ToggleableItemState.NotSelected &&
@ -230,6 +272,7 @@ export class FilterableDropdownSelectionModel {
set logicalOperator(operator: LogicalOperator) {
this.temporaryLogicalOperator = operator
this.setNullItem()
}
toggleOperator() {
@ -242,6 +285,7 @@ export class FilterableDropdownSelectionModel {
set intersection(intersection: Intersection) {
this.temporaryIntersection = intersection
this.setNullItem()
}
toggleIntersection() {
@ -250,9 +294,20 @@ export class FilterableDropdownSelectionModel {
this.intersection == Intersection.Include
? ToggleableItemState.Selected
: ToggleableItemState.Excluded
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)
}
@ -274,6 +329,7 @@ export class FilterableDropdownSelectionModel {
this.temporarySelectionStates.clear()
this.temporaryLogicalOperator = this._logicalOperator = LogicalOperator.And
this.temporaryIntersection = this._intersection = Intersection.Include
this.setNullItem()
if (fireEvent) {
this.changed.next(this)
}
@ -305,8 +361,10 @@ export class FilterableDropdownSelectionModel {
isNoneSelected() {
return (
this.selectionSize() == 1 &&
this.get(null) == ToggleableItemState.Selected
(this.selectionSize() == 1 &&
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
@Input()
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,
})
}
}
_selectionModel: FilterableDropdownSelectionModel
get items(): MatchingModel[] {
return this._selectionModel.items
}
_selectionModel: FilterableDropdownSelectionModel =
new FilterableDropdownSelectionModel()
@Input()
@Input({ required: true })
set selectionModel(model: FilterableDropdownSelectionModel) {
if (this.selectionModel) {
this.selectionModel.changed.complete()
@ -423,11 +469,6 @@ export class FilterableDropdownComponent
@Output()
selectionModelChange = new EventEmitter<FilterableDropdownSelectionModel>()
@Input()
set manyToOne(manyToOne: boolean) {
this.selectionModel.manyToOne = manyToOne
}
get manyToOne() {
return this.selectionModel.manyToOne
}
@ -484,7 +525,7 @@ export class FilterableDropdownComponent
return this.manyToOne
? this.selectionModel.selectionSize() > 1 &&
this.selectionModel.getExcludedItems().length == 0
: !this.selectionModel.isNoneSelected()
: true
}
get name(): string {
@ -545,6 +586,8 @@ export class FilterableDropdownComponent
this.selectionModel.reset()
this.modelIsDirty = false
}
this.selectionModel.singleSelect =
this.editing && !this.selectionModel.manyToOne
this.opened.next(this)
} else {
if (this.creating) {

View File

@ -30,25 +30,24 @@
[placeholder]="placeholder"
[notFoundText]="notFoundText"
[multiple]="true"
bindValue="id"
[compareWith]="compareDocuments"
[trackByFn]="trackByFn"
[minTermLength]="2"
[loading]="loading"
[typeahead]="documentsInput$"
(mousedown)="$event.stopImmediatePropagation()"
(change)="onChange(selectedDocuments)">
(change)="onChange(selectedDocumentIDs)">
<ng-template ng-label-tmp let-document="item">
<div class="d-flex align-items-center">
@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) {
<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>
</a>
} @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>
</span>
}

View File

@ -74,6 +74,11 @@ describe('DocumentLinkComponent', () => {
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', () => {
const listSpy = jest.spyOn(documentService, 'listFiltered')
listSpy.mockImplementation(

View File

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

View File

@ -17,7 +17,7 @@
(change)="onChange(value)">
<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>
@if (item.id && tags) {
<pngx-tag style="background-color: none;" [tag]="getTag(item.id)"></pngx-tag>
@ -33,7 +33,7 @@
</ng-template>
</ng-select>
@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>
</button>
}

View File

@ -154,11 +154,11 @@ describe('TagsComponent', () => {
it('support remove tags', () => {
component.tags = tags
component.value = [1, 2]
component.removeTag(new PointerEvent('point'), 2)
component.removeTag(2)
expect(component.value).toEqual([1])
component.disabled = true
component.removeTag(new PointerEvent('point'), 1)
component.removeTag(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
// prevent opening dropdown
event.stopImmediatePropagation()
let index = this.value.indexOf(id)
let index = this.value.indexOf(tagID)
if (index > -1) {
let oldValue = this.value
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, {
backdrop: 'static',
})
@ -146,9 +143,10 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
return firstValueFrom(
(modal.componentInstance as TagEditDialogComponent).succeeded.pipe(
first(),
tap(() => {
tap((newTag) => {
this.tagService.listAll().subscribe((tags) => {
this.tags = tags.results
add && this.addTag(newTag.id)
})
})
)

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>
}
@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) {
<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', () => {
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
fixture.debugElement
.query(By.css('input'))
.nativeElement.dispatchEvent(new Event('change'))
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'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()
})

View File

@ -134,9 +134,11 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
}
public onFileSelected(event: Event) {
this.uploadDocumentsService.uploadFiles(
(event.target as HTMLInputElement).files
)
const 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 {

View File

@ -9,9 +9,9 @@
}
<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>
<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) {
<option [value]="setting" [attr.selected]="isZoomSelected(setting) ? 'selected' : null">
<option [value]="setting">
{{ getZoomSettingTitle(setting) }}
</option>
}
@ -129,8 +129,8 @@
<div>
<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-date i18n-title title="Date created" formControlName="created_date" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
[error]="error?.created_date"></pngx-input-date>
<pngx-input-date i18n-title title="Date created" formControlName="created" [suggestions]="suggestions?.dates" [showFilter]="true" [horizontal]="true" (filterDocuments)="filterDocuments($event)"
[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)"
(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)"

View File

@ -1,5 +1,10 @@
import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import {
HttpHeaders,
HttpResponse,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http'
import {
HttpTestingController,
provideHttpClientTesting,
@ -451,11 +456,11 @@ describe('DocumentDetailComponent', () => {
initNormally()
component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update')
const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showInfo')
updateSpy.mockImplementation((o) => of(doc))
patchSpy.mockImplementation((o) => of(doc))
component.save(true)
expect(updateSpy).toHaveBeenCalled()
expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.'
@ -466,11 +471,11 @@ describe('DocumentDetailComponent', () => {
initNormally()
component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update')
const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showInfo')
updateSpy.mockImplementation((o) => of(doc))
patchSpy.mockImplementation((o) => of(doc))
component.save()
expect(updateSpy).toHaveBeenCalled()
expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.'
@ -482,12 +487,12 @@ describe('DocumentDetailComponent', () => {
initNormally()
component.title = 'Foo Bar'
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 error = new Error('failed to save')
updateSpy.mockImplementation(() => throwError(() => error))
patchSpy.mockImplementation(() => throwError(() => error))
component.save()
expect(updateSpy).toHaveBeenCalled()
expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
'Error saving document "Doc 3"',
@ -500,13 +505,13 @@ describe('DocumentDetailComponent', () => {
initNormally()
component.title = 'Foo Bar'
const closeSpy = jest.spyOn(component, 'close')
const updateSpy = jest.spyOn(documentService, 'update')
const patchSpy = jest.spyOn(documentService, 'patch')
const toastSpy = jest.spyOn(toastService, 'showInfo')
updateSpy.mockImplementation(() =>
patchSpy.mockImplementation(() =>
throwError(() => new Error('failed to save'))
)
component.save(true)
expect(updateSpy).toHaveBeenCalled()
expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
'Document "Doc 3" saved successfully.'
@ -517,8 +522,8 @@ describe('DocumentDetailComponent', () => {
initNormally()
const nextDocId = 100
component.title = 'Foo Bar'
const updateSpy = jest.spyOn(documentService, 'update')
updateSpy.mockReturnValue(of(doc))
const patchSpy = jest.spyOn(documentService, 'patch')
patchSpy.mockReturnValue(of(doc))
const nextSpy = jest.spyOn(documentListViewService, 'getNext')
nextSpy.mockReturnValue(of(nextDocId))
const closeSpy = jest.spyOn(openDocumentsService, 'closeDocument')
@ -526,7 +531,7 @@ describe('DocumentDetailComponent', () => {
const navigateSpy = jest.spyOn(router, 'navigate')
component.saveEditNext()
expect(updateSpy).toHaveBeenCalled()
expect(patchSpy).toHaveBeenCalled()
expect(navigateSpy).toHaveBeenCalledWith(['documents', nextDocId])
expect
})
@ -536,12 +541,12 @@ describe('DocumentDetailComponent', () => {
initNormally()
component.title = 'Foo Bar'
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 error = new Error('failed to save')
updateSpy.mockImplementation(() => throwError(() => error))
patchSpy.mockImplementation(() => throwError(() => error))
component.saveEditNext()
expect(updateSpy).toHaveBeenCalled()
expect(patchSpy).toHaveBeenCalled()
expect(closeSpy).not.toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith('Error saving document', error)
})
@ -786,14 +791,9 @@ describe('DocumentDetailComponent', () => {
it('should select correct zoom setting in dropdown', () => {
initNormally()
component.setZoom(ZoomSetting.PageFit)
expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeTruthy()
expect(component.isZoomSelected(ZoomSetting.One)).toBeFalsy()
component.setZoom(ZoomSetting.PageWidth)
expect(component.isZoomSelected(ZoomSetting.One)).toBeTruthy()
expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeFalsy()
expect(component.currentZoom).toEqual(ZoomSetting.PageFit)
component.setZoom(ZoomSetting.Quarter)
expect(component.isZoomSelected(ZoomSetting.Quarter)).toBeTruthy()
expect(component.isZoomSelected(ZoomSetting.PageFit)).toBeFalsy()
expect(component.currentZoom).toEqual(ZoomSetting.Quarter)
})
it('should support updating notes dynamically', () => {
@ -965,10 +965,10 @@ describe('DocumentDetailComponent', () => {
expect(fixture.debugElement.nativeElement.textContent).toContain(
customFields[1].name
)
const updateSpy = jest.spyOn(documentService, 'update')
const patchSpy = jest.spyOn(documentService, 'patch')
component.save(true)
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
expect(updateSpy.mock.lastCall[0].custom_fields[1]).toEqual({
expect(patchSpy.mock.lastCall[0].custom_fields).toHaveLength(2)
expect(patchSpy.mock.lastCall[0].custom_fields[1]).toEqual({
field: customFields[1].id,
value: null,
})
@ -985,13 +985,51 @@ describe('DocumentDetailComponent', () => {
expect(
fixture.debugElement.query(By.css('form')).nativeElement.textContent
).not.toContain('Field 1')
const updateSpy = jest.spyOn(documentService, 'update')
const patchSpy = jest.spyOn(documentService, 'patch')
component.save(true)
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(
expect(patchSpy.mock.lastCall[0].custom_fields).toHaveLength(
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', () => {
initNormally()
component.error = {
@ -1331,6 +1369,34 @@ describe('DocumentDetailComponent', () => {
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', () => {
jest.spyOn(settingsService, 'get').mockReturnValue(true)
expect(component.emailEnabled).toBeTruthy()

View File

@ -1,5 +1,5 @@
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 {
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 { SettingsService } from 'src/app/services/settings.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 * as UTIF from 'utif'
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
@ -207,7 +208,7 @@ export class DocumentDetailComponent
documentForm: FormGroup = new FormGroup({
title: new FormControl(''),
content: new FormControl(''),
created_date: new FormControl(),
created: new FormControl(),
correspondent: new FormControl(),
document_type: new FormControl(),
storage_path: new FormControl(),
@ -489,7 +490,7 @@ export class DocumentDetailComponent
this.store = new BehaviorSubject({
title: doc.title,
content: doc.content,
created_date: doc.created_date,
created: doc.created,
correspondent: doc.correspondent,
document_type: doc.document_type,
storage_path: doc.storage_path,
@ -783,6 +784,7 @@ export class DocumentDetailComponent
this.title = doc.title
this.updateFormForCustomFields()
this.documentForm.patchValue(doc)
this.documentForm.markAsPristine()
this.openDocumentService.setDirty(doc, false)
},
error: () => {
@ -793,11 +795,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) {
this.networkActive = true
;(document.activeElement as HTMLElement)?.dispatchEvent(new Event('change'))
this.documentsService
.update(this.document)
.patch(this.getChangedFields())
.pipe(first())
.subscribe({
next: (docValues) => {
@ -824,11 +845,18 @@ export class DocumentDetailComponent
},
error: (error) => {
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(
$localize`Document "${this.document.title}" saved successfully.`
)
close && this.close()
this.close()
} else {
this.error = error.error
this.toastService.showError(
@ -844,7 +872,7 @@ export class DocumentDetailComponent
this.networkActive = true
this.store.next(this.documentForm.value)
this.documentsService
.update(this.document)
.patch(this.getChangedFields())
.pipe(
switchMap((updateResult) => {
return this.documentListViewService
@ -988,44 +1016,46 @@ export class DocumentDetailComponent
this.documentId,
original
)
this.http.get(downloadUrl, { responseType: 'blob' }).subscribe({
next: (blob) => {
this.downloading = false
const blobParts = [blob]
const file = new File(
blobParts,
original
? this.document.original_file_name
: this.document.archived_file_name,
{
type: original ? this.document.mime_type : 'application/pdf',
}
)
if (
!this.deviceDetectorService.isDesktop() &&
navigator.canShare &&
navigator.canShare({ files: [file] })
) {
navigator.share({
files: [file],
this.http
.get(downloadUrl, { observe: 'response', responseType: 'blob' })
.subscribe({
next: (response: HttpResponse<Blob>) => {
const contentDisposition = response.headers.get('Content-Disposition')
const filename =
getFilenameFromContentDisposition(contentDisposition) ||
this.document.title
const blob = new Blob([response.body], {
type: response.body.type,
})
} else {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = this.document.title
a.click()
URL.revokeObjectURL(url)
}
},
error: (error) => {
this.downloading = false
this.toastService.showError(
$localize`Error downloading document`,
error
)
},
})
this.downloading = false
const file = new File([blob], filename, {
type: response.body.type,
})
if (
!this.deviceDetectorService.isDesktop() &&
navigator.canShare &&
navigator.canShare({ files: [file] })
) {
navigator.share({
files: [file],
})
} else {
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() {
@ -1089,12 +1119,10 @@ export class DocumentDetailComponent
)
}
isZoomSelected(setting: ZoomSetting): boolean {
get currentZoom() {
if (this.previewZoomScale === ZoomSetting.PageFit) {
return setting === ZoomSetting.PageFit
}
return this.previewZoomSetting === setting
return ZoomSetting.PageFit
} else return this.previewZoomSetting
}
getZoomSettingTitle(setting: ZoomSetting): string {
@ -1279,9 +1307,7 @@ export class DocumentDetailComponent
this.document.custom_fields?.forEach((fieldInstance) => {
this.customFieldFormFields.push(
new FormGroup({
field: new FormControl(
this.getCustomFieldFromInstance(fieldInstance)?.id
),
field: new FormControl(fieldInstance.field),
value: new FormControl(fieldInstance.value),
}),
{ emitEvent }
@ -1297,6 +1323,8 @@ export class DocumentDetailComponent
created: new Date(),
})
this.updateFormForCustomFields(true)
this.documentForm.get('custom_fields').markAsDirty()
this.documentForm.updateValueAndValidity()
}
public removeField(fieldInstance: CustomFieldInstance) {
@ -1305,6 +1333,7 @@ export class DocumentDetailComponent
1
)
this.updateFormForCustomFields(true)
this.documentForm.get('custom_fields').markAsDirty()
this.documentForm.updateValueAndValidity()
}

View File

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

View File

@ -1150,10 +1150,10 @@ describe('BulkEditorComponent', () => {
it('should not attempt to retrieve objects if user does not have permissions', () => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.tags).toBeUndefined()
expect(component.correspondents).toBeUndefined()
expect(component.documentTypes).toBeUndefined()
expect(component.storagePaths).toBeUndefined()
expect(component.tagSelectionModel.items.length).toEqual(0)
expect(component.correspondentSelectionModel.items.length).toEqual(0)
expect(component.documentTypeSelectionModel.items.length).toEqual(0)
expect(component.storagePathsSelectionModel.items.length).toEqual(0)
httpTestingController.expectNone(`${environment.apiBaseUrl}documents/tags/`)
httpTestingController.expectNone(
`${environment.apiBaseUrl}documents/correspondents/`
@ -1204,7 +1204,9 @@ describe('BulkEditorComponent', () => {
expect(tagListAllSpy).toHaveBeenCalled()
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', () => {
@ -1251,7 +1253,9 @@ describe('BulkEditorComponent', () => {
expect(correspondentSelectionModelToggleSpy).toHaveBeenCalledWith(
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', () => {
@ -1295,7 +1299,9 @@ describe('BulkEditorComponent', () => {
expect(documentTypeSelectionModelToggleSpy).toHaveBeenCalledWith(
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', () => {
@ -1339,7 +1345,9 @@ describe('BulkEditorComponent', () => {
expect(storagePathsSelectionModelToggleSpy).toHaveBeenCalledWith(
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', () => {
@ -1391,7 +1399,9 @@ describe('BulkEditorComponent', () => {
expect(customFieldsSelectionModelToggleSpy).toHaveBeenCalledWith(
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', () => {
@ -1416,17 +1426,17 @@ describe('BulkEditorComponent', () => {
const toastServiceShowErrorSpy = jest.spyOn(toastService, 'showError')
const listReloadSpy = jest.spyOn(documentListViewService, 'reload')
component.customFields = [
component.customFieldsSelectionModel.items = [
{ id: 1, name: 'Custom Field 1', data_type: CustomFieldDataType.String },
{ id: 2, name: 'Custom Field 2', data_type: CustomFieldDataType.String },
]
] as any
component.setCustomFieldValues({
itemsToAdd: [{ id: 1 }, { id: 2 }],
itemsToRemove: [1],
} 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.documents).toEqual([3, 4])

View File

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

View File

@ -15,7 +15,7 @@
}
</div>
<div class="col">
<div class="col col-md-10">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title w-100">
@ -112,14 +112,14 @@
@if (displayFields.includes(DisplayField.CREATED) || displayFields.includes(DisplayField.ADDED)) {
<ng-template #dateTooltip>
<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>Modified: {{ document.modified | customDate }}</span>
</div>
</ng-template>
@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">
<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>
}
@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">
<ng-template #dateTooltip>
<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>Modified: {{ document.modified | customDate }}</span>
</div>
</ng-template>
<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>
<small>{{document.created_date | customDate:'mediumDate'}}</small>
<small>{{document.created | customDate:'mediumDate'}}</small>
</div>
</div>
}

View File

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

View File

@ -35,11 +35,9 @@
<div class="col-auto">
<div class="d-flex flex-wrap gap-3">
<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
filterPlaceholder="Filter tags" i18n-filterPlaceholder
[items]="tags"
[manyToOne]="true"
[(selectionModel)]="tagSelectionModel"
(selectionModelChange)="updateRules()"
(opened)="onTagsDropdownOpen()"
@ -48,10 +46,9 @@
[disabled]="disabled"
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
filterPlaceholder="Filter correspondents" i18n-filterPlaceholder
[items]="correspondents"
[(selectionModel)]="correspondentSelectionModel"
(selectionModelChange)="updateRules()"
(opened)="onCorrespondentDropdownOpen()"
@ -60,10 +57,9 @@
[disabled]="disabled"
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
filterPlaceholder="Filter document types" i18n-filterPlaceholder
[items]="documentTypes"
[(selectionModel)]="documentTypeSelectionModel"
(selectionModelChange)="updateRules()"
(opened)="onDocumentTypeDropdownOpen()"
@ -72,10 +68,9 @@
[disabled]="disabled"
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
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
[items]="storagePaths"
[(selectionModel)]="storagePathSelectionModel"
(selectionModelChange)="updateRules()"
(opened)="onStoragePathDropdownOpen()"

View File

@ -69,6 +69,7 @@ import {
FILTER_STORAGE_PATH,
FILTER_TITLE,
FILTER_TITLE_CONTENT,
NEGATIVE_NULL_FILTER_VALUE,
} from 'src/app/data/filter-rule-type'
import { StoragePath } from 'src/app/data/storage-path'
import { Tag } from 'src/app/data/tag'
@ -671,9 +672,6 @@ describe('FilterEditorComponent', () => {
value: '12',
},
]
expect(component.correspondentSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.correspondentSelectionModel.intersection).toEqual(
Intersection.Include
)
@ -681,6 +679,19 @@ describe('FilterEditorComponent', () => {
correspondents[0],
])
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(() => {
@ -754,9 +765,6 @@ describe('FilterEditorComponent', () => {
value: '22',
},
]
expect(component.documentTypeSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.documentTypeSelectionModel.intersection).toEqual(
Intersection.Include
)
@ -764,6 +772,19 @@ describe('FilterEditorComponent', () => {
document_types[0],
])
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(() => {
@ -780,9 +801,6 @@ describe('FilterEditorComponent', () => {
value: '23',
},
]
expect(component.documentTypeSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.documentTypeSelectionModel.intersection).toEqual(
Intersection.Include
)
@ -837,9 +855,6 @@ describe('FilterEditorComponent', () => {
value: '32',
},
]
expect(component.storagePathSelectionModel.logicalOperator).toEqual(
LogicalOperator.Or
)
expect(component.storagePathSelectionModel.intersection).toEqual(
Intersection.Include
)
@ -847,6 +862,19 @@ describe('FilterEditorComponent', () => {
storage_paths[0],
])
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(() => {
@ -1398,6 +1426,19 @@ describe('FilterEditorComponent', () => {
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(() => {
@ -1455,6 +1496,19 @@ describe('FilterEditorComponent', () => {
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(() => {
@ -1512,6 +1566,19 @@ describe('FilterEditorComponent', () => {
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(() => {

View File

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

View File

@ -2,13 +2,6 @@
<ng-content select="[content]"></ng-content>
</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>
</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 {
opacity: 0;
transition: opacity 0.25s ease-in-out;
background-color: hsla(var(--pngx-primary), var(--pngx-primary-lightness), .8);
z-index: 1200;
h2 {
color: var(--pngx-primary-text-contrast)
}
&.active {
opacity: 1;
}
}

View File

@ -9,7 +9,6 @@ import {
tick,
} from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop'
import { PermissionsService } from 'src/app/services/permissions.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
@ -27,7 +26,7 @@ describe('FileDropComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NgxFileDropModule, FileDropComponent, ToastsComponent],
imports: [FileDropComponent, ToastsComponent],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
@ -66,12 +65,12 @@ describe('FileDropComponent', () => {
const dropzone = fixture.debugElement.query(
By.css('.global-dropzone-overlay')
)
expect(dropzone.classes['hide']).toBeTruthy()
expect(dropzone.classes['active']).toBeFalsy()
component.onDragLeave(new Event('dragleave') as DragEvent)
tick(700)
fixture.detectChanges()
// drop
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
const dragEvent = new Event('drop')
dragEvent['dataTransfer'] = {
files: {
@ -93,53 +92,209 @@ describe('FileDropComponent', () => {
tick(1)
fixture.detectChanges()
expect(component.fileIsOver).toBeTruthy()
const dropzone = fixture.debugElement.query(
By.css('.global-dropzone-overlay')
)
component.onDragLeave(new Event('dragleave') as DragEvent)
tick(700)
fixture.detectChanges()
expect(dropzone.classes['hide']).toBeTruthy()
// drop
const toastSpy = jest.spyOn(toastService, 'show')
const uploadSpy = jest.spyOn(
UploadDocumentsService.prototype as any,
'uploadFile'
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
const dragEvent = new Event('drop')
dragEvent['dataTransfer'] = {
files: {
item: () => {
return new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
items: [
{
kind: 'file',
type: 'application/pdf',
getAsFile: () => file,
},
length: 1,
} as unknown as FileList,
],
}
component.onDrop(dragEvent as DragEvent)
component.dropped([
{
fileEntry: {
isFile: true,
file: (callback) => {
callback(
new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
)
},
},
} as unknown as NgxFileDropEntry,
])
tick(3000)
expect(toastSpy).toHaveBeenCalled()
expect(uploadSpy).toHaveBeenCalled()
discardPeriodicTasks()
}))
it('should support drag drop, initiate upload with webkitGetAsEntry', fakeAsync(() => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.fileIsOver).toBeFalsy()
const overEvent = new Event('dragover') as DragEvent
;(overEvent as any).dataTransfer = { types: ['Files'] }
component.onDragOver(overEvent)
tick(1)
fixture.detectChanges()
expect(component.fileIsOver).toBeTruthy()
component.onDragLeave(new Event('dragleave') as DragEvent)
tick(700)
fixture.detectChanges()
// drop
const toastSpy = jest.spyOn(toastService, 'show')
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
const dragEvent = new Event('drop')
dragEvent['dataTransfer'] = {
items: [
{
kind: 'file',
type: 'application/pdf',
webkitGetAsEntry: () => ({
isFile: true,
isDirectory: false,
file: (cb: (file: File) => void) => cb(file),
}),
},
],
files: [],
}
component.onDrop(dragEvent as DragEvent)
tick(3000)
expect(toastSpy).toHaveBeenCalled()
expect(uploadSpy).toHaveBeenCalled()
discardPeriodicTasks()
}))
it('should show an error on traverseFileTree error', fakeAsync(() => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
const toastSpy = jest.spyOn(toastService, 'showError')
const traverseSpy = jest
.spyOn(component as any, 'traverseFileTree')
.mockReturnValue(Promise.reject(new Error('Error traversing file tree')))
fixture.detectChanges()
// Simulate a drop with a directory entry
const mockEntry = {
isDirectory: true,
isFile: false,
createReader: () => ({ readEntries: jest.fn() }),
} as unknown as FileSystemDirectoryEntry
const event = {
preventDefault: () => {},
stopImmediatePropagation: () => {},
dataTransfer: {
items: [
{
kind: 'file',
webkitGetAsEntry: () => mockEntry,
},
],
},
} as unknown as DragEvent
component.onDrop(event)
tick() // flush microtasks (e.g., Promise.reject)
expect(traverseSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
$localize`Failed to read dropped items: Error traversing file tree`
)
discardPeriodicTasks()
}))
it('should support drag drop, initiate upload without DataTransfer API support', fakeAsync(() => {
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
expect(component.fileIsOver).toBeFalsy()
const overEvent = new Event('dragover') as DragEvent
;(overEvent as any).dataTransfer = { types: ['Files'] }
component.onDragOver(overEvent)
tick(1)
fixture.detectChanges()
expect(component.fileIsOver).toBeTruthy()
component.onDragLeave(new Event('dragleave') as DragEvent)
tick(700)
fixture.detectChanges()
// drop
const toastSpy = jest.spyOn(toastService, 'show')
const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFile')
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
const dragEvent = new Event('drop')
dragEvent['dataTransfer'] = {
items: [],
files: [file],
}
component.onDrop(dragEvent as DragEvent)
tick(3000)
expect(toastSpy).toHaveBeenCalled()
expect(uploadSpy).toHaveBeenCalled()
discardPeriodicTasks()
}))
it('should resolve a single file when entry isFile', () => {
const mockFile = new File(['data'], 'test.txt', { type: 'text/plain' })
const mockEntry = {
isFile: true,
isDirectory: false,
file: (cb: (f: File) => void) => cb(mockFile),
} as unknown as FileSystemFileEntry
return (component as any)
.traverseFileTree(mockEntry)
.then((result: File[]) => {
expect(result).toEqual([mockFile])
})
})
it('should resolve all files in a flat directory', async () => {
const file1 = new File(['data'], 'file1.txt')
const file2 = new File(['data'], 'file2.txt')
const mockFileEntry1 = {
isFile: true,
isDirectory: false,
file: (cb: (f: File) => void) => cb(file1),
} as unknown as FileSystemFileEntry
const mockFileEntry2 = {
isFile: true,
isDirectory: false,
file: (cb: (f: File) => void) => cb(file2),
} as unknown as FileSystemFileEntry
let callCount = 0
const mockDirEntry = {
isFile: false,
isDirectory: true,
createReader: () => ({
readEntries: (cb: (batch: FileSystemEntry[]) => void) => {
if (callCount++ === 0) {
cb([mockFileEntry1, mockFileEntry2])
} else {
cb([]) // second call: signal EOF
}
},
}),
} as unknown as FileSystemDirectoryEntry
const result = await (component as any).traverseFileTree(mockDirEntry)
expect(result).toEqual([file1, file2])
})
it('should resolve a non-file non-directory entry as an empty array', () => {
const mockEntry = {
isFile: false,
isDirectory: false,
file: (cb: (f: File) => void) => cb(new File([], '')),
} as unknown as FileSystemEntry
return (component as any)
.traverseFileTree(mockEntry)
.then((result: File[]) => {
expect(result).toEqual([])
})
})
it('should ignore events if disabled', fakeAsync(() => {
settingsService.globalDropzoneEnabled = false
expect(settingsService.globalDropzoneActive).toBeFalsy()

View File

@ -1,9 +1,4 @@
import { Component, HostListener, ViewChild } from '@angular/core'
import {
NgxFileDropComponent,
NgxFileDropEntry,
NgxFileDropModule,
} from 'ngx-file-drop'
import { Component, HostListener } from '@angular/core'
import {
PermissionAction,
PermissionsService,
@ -17,7 +12,7 @@ import { UploadDocumentsService } from 'src/app/services/upload-documents.servic
selector: 'pngx-file-drop',
templateUrl: './file-drop.component.html',
styleUrls: ['./file-drop.component.scss'],
imports: [NgxFileDropModule],
imports: [],
})
export class FileDropComponent {
private fileLeaveTimeoutID: any
@ -41,8 +36,6 @@ export class FileDropComponent {
)
}
@ViewChild('ngxFileDrop') ngxFileDrop: NgxFileDropComponent
@HostListener('document:dragover', ['$event']) onDragOver(event: DragEvent) {
if (!this.dragDropEnabled || !event.dataTransfer?.types?.includes('Files'))
return
@ -78,19 +71,85 @@ export class FileDropComponent {
}, ms)
}
private traverseFileTree(entry: FileSystemEntry): Promise<File[]> {
if (entry.isFile) {
return new Promise((resolve, reject) => {
;(entry as FileSystemFileEntry).file(resolve, reject)
}).then((file: File) => [file])
}
if (entry.isDirectory) {
return new Promise<File[]>((resolve, reject) => {
const dirReader = (entry as FileSystemDirectoryEntry).createReader()
const allEntries: FileSystemEntry[] = []
const readEntries = () => {
dirReader.readEntries((batch) => {
if (batch.length === 0) {
const promises = allEntries.map((child) =>
this.traverseFileTree(child)
)
Promise.all(promises)
.then((results) => resolve([].concat(...results)))
.catch(reject)
} else {
allEntries.push(...batch)
readEntries() // keep reading
}
}, reject)
}
readEntries()
})
}
return Promise.resolve([])
}
@HostListener('document:drop', ['$event']) public onDrop(event: DragEvent) {
if (!this.dragDropEnabled) return
event.preventDefault()
event.stopImmediatePropagation()
// pass event onto ngx-file-drop to handle files
this.ngxFileDrop.dropFiles(event)
this.onDragLeave(event, true)
}
public dropped(files: NgxFileDropEntry[]) {
this.uploadDocumentsService.onNgxFileDrop(files)
if (files.length > 0)
const files: File[] = []
const entries: FileSystemEntry[] = []
if (event.dataTransfer?.items && event.dataTransfer.items.length) {
for (const item of Array.from(event.dataTransfer.items)) {
if (item.webkitGetAsEntry) {
// webkitGetAsEntry not standard, but is widely supported
const entry = item.webkitGetAsEntry()
if (entry) entries.push(entry)
} else if (item.kind === 'file') {
const file = item.getAsFile()
if (file) files.push(file)
}
}
} else if (event.dataTransfer?.files) {
// Fallback for browsers without DataTransferItem API
for (const file of Array.from(event.dataTransfer.files)) {
files.push(file)
}
}
if (entries.length) {
const promises = entries.map((entry) => this.traverseFileTree(entry))
Promise.all(promises)
.then((results) => {
files.push(...[].concat(...results))
this.toastService.showInfo($localize`Initiating upload...`, 3000)
files.forEach((file) => this.uploadDocumentsService.uploadFile(file))
})
.catch((e) => {
this.toastService.showError(
$localize`Failed to read dropped items: ${e.message}`
)
})
} else if (files.length) {
this.toastService.showInfo($localize`Initiating upload...`, 3000)
files.forEach((file) => this.uploadDocumentsService.uploadFile(file))
}
this.onDragLeave(event, true)
}
@HostListener('window:blur', ['$event']) public onWindowBlur() {

View File

@ -188,7 +188,7 @@ describe('MailComponent', () => {
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
editDialog.failed.emit()
expect(toastErrorSpy).toBeCalled()
editDialog.succeeded.emit(mailAccounts[0])
editDialog.succeeded.emit(mailAccounts[0] as any)
expect(toastInfoSpy).toHaveBeenCalledWith(
`Saved account "${mailAccounts[0].name}".`
)
@ -246,7 +246,7 @@ describe('MailComponent', () => {
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
editDialog.failed.emit()
expect(toastErrorSpy).toBeCalled()
editDialog.succeeded.emit(mailRules[0])
editDialog.succeeded.emit(mailRules[0] as any)
expect(toastInfoSpy).toHaveBeenCalledWith(
`Saved rule "${mailRules[0].name}".`
)

View File

@ -130,9 +130,6 @@ export interface Document extends ObjectWithPermissions {
// UTC
created?: Date
// localized date
created_date?: Date
modified?: Date
added?: Date

View File

@ -1,5 +1,7 @@
import { DataType } from './datatype'
export const NEGATIVE_NULL_FILTER_VALUE = -1
// These correspond to src/documents/models.py and changes here require a DB migration (and vice versa)
export const FILTER_TITLE = 0
export const FILTER_CONTENT = 1

View File

@ -49,6 +49,7 @@ export enum ConfigOptionType {
export const ConfigCategory = {
General: $localize`General Settings`,
OCR: $localize`OCR Settings`,
Barcode: $localize`Barcode Settings`,
}
export interface ConfigOption {
@ -180,6 +181,83 @@ export const PaperlessConfigOptions: ConfigOption[] = [
config_key: 'PAPERLESS_APP_TITLE',
category: ConfigCategory.General,
},
{
key: 'barcodes_enabled',
title: $localize`Enable Barcodes`,
type: ConfigOptionType.Boolean,
config_key: 'PAPERLESS_CONSUMER_ENABLE_BARCODES',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_enable_tiff_support',
title: $localize`Enable TIFF Support`,
type: ConfigOptionType.Boolean,
config_key: 'PAPERLESS_CONSUMER_BARCODE_TIFF_SUPPORT',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_string',
title: $localize`Barcode String`,
type: ConfigOptionType.String,
config_key: 'PAPERLESS_CONSUMER_BARCODE_STRING',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_retain_split_pages',
title: $localize`Retain Split Pages`,
type: ConfigOptionType.Boolean,
config_key: 'PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_enable_asn',
title: $localize`Enable ASN`,
type: ConfigOptionType.Boolean,
config_key: 'PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_asn_prefix',
title: $localize`ASN Prefix`,
type: ConfigOptionType.String,
config_key: 'PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_upscale',
title: $localize`Upscale`,
type: ConfigOptionType.Number,
config_key: 'PAPERLESS_CONSUMER_BARCODE_UPSCALE',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_dpi',
title: $localize`DPI`,
type: ConfigOptionType.Number,
config_key: 'PAPERLESS_CONSUMER_BARCODE_DPI',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_max_pages',
title: $localize`Max Pages`,
type: ConfigOptionType.Number,
config_key: 'PAPERLESS_CONSUMER_BARCODE_MAX_PAGES',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_enable_tag',
title: $localize`Enable Tag Detection`,
type: ConfigOptionType.Boolean,
config_key: 'PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE',
category: ConfigCategory.Barcode,
},
{
key: 'barcode_tag_mapping',
title: $localize`Tag Mapping`,
type: ConfigOptionType.JSON,
config_key: 'PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING',
category: ConfigCategory.Barcode,
},
]
export interface PaperlessConfig extends ObjectWithId {
@ -198,4 +276,15 @@ export interface PaperlessConfig extends ObjectWithId {
user_args: object
app_logo: string
app_title: string
barcodes_enabled: boolean
barcode_enable_tiff_support: boolean
barcode_string: string
barcode_retain_split_pages: boolean
barcode_enable_asn: boolean
barcode_asn_prefix: string
barcode_upscale: number
barcode_dpi: number
barcode_max_pages: number
barcode_enable_tag: boolean
barcode_tag_mapping: object
}

View File

@ -7,4 +7,6 @@ export interface WebsocketProgressMessage {
message?: string
document_id: number
owner_id?: number
users_can_view?: number[]
groups_can_view?: number[]
}

View File

@ -50,7 +50,7 @@ describe('ObjectNamePipe', () => {
})
})
it('should return empty string if object not found', (done) => {
it('should return Private string if object not found', (done) => {
const mockObjects = {
results: [{ id: 2, name: 'Object 2' }],
count: 1,
@ -60,7 +60,7 @@ describe('ObjectNamePipe', () => {
jest.spyOn(objectService, 'listAll').mockReturnValue(of(mockObjects))
pipe.transform(1).subscribe((result) => {
expect(result).toBe('')
expect(result).toBe('Private')
done()
})
})

View File

@ -35,7 +35,10 @@ export abstract class ObjectNamePipe implements PipeTransform {
return this.objectService.listAll().pipe(
map((objects) => {
this.objects = objects.results
return this.objects.find((o) => o.id === obejctId)?.name || ''
return (
this.objects.find((o) => o.id === obejctId)?.name ||
$localize`Private`
)
}),
catchError(() => of(''))
)

View File

@ -268,15 +268,15 @@ describe(`DocumentService`, () => {
expect(req.request.method).toEqual('GET')
})
it('should pass remove_inbox_tags setting to update', () => {
subscription = service.update(documents[0]).subscribe()
it('should pass remove_inbox_tags setting to patch', () => {
subscription = service.patch(documents[0]).subscribe()
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}${endpoint}/${documents[0].id}/`
)
expect(req.request.body.remove_inbox_tags).toEqual(false)
settingsService.set(SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS, true)
subscription = service.update(documents[0]).subscribe()
subscription = service.patch(documents[0]).subscribe()
req = httpTestingController.expectOne(
`${environment.apiBaseUrl}${endpoint}/${documents[0].id}/`
)

View File

@ -189,13 +189,11 @@ export class DocumentService extends AbstractPaperlessService<Document> {
return this.http.get<number>(this.getResourceUrl(null, 'next_asn'))
}
update(o: Document): Observable<Document> {
// we want to only set created_date
o.created = undefined
patch(o: Document): Observable<Document> {
o.remove_inbox_tags = !!this.settingsService.get(
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
)
return super.update(o)
return super.patch(o)
}
uploadDocument(formData) {

View File

@ -156,6 +156,72 @@ describe(`Additional service tests for SavedViewService`, () => {
httpTestingController.verify() // no reload
})
it('should reload after create, delete, patch and patchMany', () => {
const reloadSpy = jest.spyOn(service, 'reload')
service
.create({
name: 'New Saved View',
show_on_dashboard: true,
show_in_sidebar: true,
sort_field: 'name',
sort_reverse: true,
filter_rules: [],
})
.subscribe()
httpTestingController
.expectOne(`${environment.apiBaseUrl}${endpoint}/`)
.flush({})
expect(reloadSpy).toHaveBeenCalled()
reloadSpy.mockClear()
httpTestingController
.expectOne(
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
)
.flush({
results: saved_views,
})
service.delete(saved_views[0]).subscribe()
httpTestingController
.expectOne(`${environment.apiBaseUrl}${endpoint}/1/`)
.flush({})
expect(reloadSpy).toHaveBeenCalled()
reloadSpy.mockClear()
httpTestingController
.expectOne(
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
)
.flush({
results: saved_views,
})
service.patch(saved_views[0], true).subscribe()
httpTestingController
.expectOne(`${environment.apiBaseUrl}${endpoint}/1/`)
.flush({})
expect(reloadSpy).toHaveBeenCalled()
httpTestingController
.expectOne(
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
)
.flush({
results: saved_views,
})
service.patchMany(saved_views).subscribe()
saved_views.forEach((saved_view) => {
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}${endpoint}/${saved_view.id}/`
)
req.flush({})
})
expect(reloadSpy).toHaveBeenCalled()
httpTestingController
.expectOne(
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=100000`
)
.flush({
results: saved_views,
})
})
beforeEach(() => {
// Dont need to setup again

View File

@ -602,7 +602,6 @@ export class SettingsService {
)
} catch (error) {
this.toastService.showError(errorMessage)
console.log(error)
}
this.storeSettings()
@ -614,7 +613,6 @@ export class SettingsService {
},
error: (e) => {
this.toastService.showError(errorMessage)
console.log(e)
},
})
}
@ -636,7 +634,6 @@ export class SettingsService {
this.toastService.showError(
'Error migrating update checking setting'
)
console.log(e)
},
})
}

View File

@ -15,33 +15,6 @@ import {
WebsocketStatusService,
} from './websocket-status.service'
const files = [
{
lastModified: 1693349892540,
lastModifiedDate: new Date(),
name: 'file1.pdf',
size: 386,
type: 'application/pdf',
},
{
lastModified: 1695618533892,
lastModifiedDate: new Date(),
name: 'file2.pdf',
size: 358265,
type: 'application/pdf',
},
]
const fileList = {
item: (x) => {
return new File(
[new Blob(['testing'], { type: files[x].type })],
files[x].name
)
},
length: files.length,
} as unknown as FileList
describe('UploadDocumentsService', () => {
let httpTestingController: HttpTestingController
let uploadDocumentsService: UploadDocumentsService
@ -68,7 +41,11 @@ describe('UploadDocumentsService', () => {
})
it('calls post_document api endpoint on upload', () => {
uploadDocumentsService.uploadFiles(fileList)
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
uploadDocumentsService.uploadFile(file)
const req = httpTestingController.match(
`${environment.apiBaseUrl}documents/post_document/`
)
@ -78,7 +55,16 @@ describe('UploadDocumentsService', () => {
})
it('updates progress during upload and failure', () => {
uploadDocumentsService.uploadFiles(fileList)
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
const file2 = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file2.pdf'
)
uploadDocumentsService.uploadFile(file)
uploadDocumentsService.uploadFile(file2)
expect(websocketStatusService.getConsumerStatusNotCompleted()).toHaveLength(
2
@ -103,7 +89,11 @@ describe('UploadDocumentsService', () => {
})
it('updates progress on failure', () => {
uploadDocumentsService.uploadFiles(fileList)
const file = new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
uploadDocumentsService.uploadFile(file)
let req = httpTestingController.match(
`${environment.apiBaseUrl}documents/post_document/`
@ -125,7 +115,7 @@ describe('UploadDocumentsService', () => {
websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(1)
uploadDocumentsService.uploadFiles(fileList)
uploadDocumentsService.uploadFile(file)
req = httpTestingController.match(
`${environment.apiBaseUrl}documents/post_document/`
@ -143,35 +133,4 @@ describe('UploadDocumentsService', () => {
websocketStatusService.getConsumerStatus(FileStatusPhase.FAILED)
).toHaveLength(2)
})
it('accepts files via drag and drop', () => {
const uploadSpy = jest.spyOn(
UploadDocumentsService.prototype as any,
'uploadFile'
)
const fileEntry = {
name: 'file.pdf',
isDirectory: false,
isFile: true,
file: (callback) => {
return callback(
new File(
[new Blob(['testing'], { type: 'application/pdf' })],
'file.pdf'
)
)
},
}
uploadDocumentsService.onNgxFileDrop([
{
relativePath: 'path/to/file.pdf',
fileEntry,
},
])
expect(uploadSpy).toHaveBeenCalled()
let req = httpTestingController.match(
`${environment.apiBaseUrl}documents/post_document/`
)
})
})

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