Compare commits

...

575 Commits

Author SHA1 Message Date
shamoon
47ce797ee9 Bump version to 2.8.2 2024-05-07 17:13:59 -07:00
github-actions[bot]
f8057ed4f1 New Crowdin translations by GitHub Action (#6606)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-05-07 17:09:33 -07:00
Trenton H
d3ff0ff8e0 Restores the compression of static files for x86_64 (#6627) 2024-05-07 15:17:15 -07:00
shamoon
6ea25a96a3 Fix: make backend monetary validation accept unpadded decimals (#6626) 2024-05-07 21:38:52 +00:00
shamoon
caec0ed4d1 Fix: allow bulk edit with existing fields (#6625) 2024-05-07 14:26:07 -07:00
shamoon
ce08400f4e Enhancement: show name on cards if custom field empty, add tooltip (#6620) 2024-05-07 11:09:15 -07:00
shamoon
076b5b1af5 Security: Disable eval in pdfjs (#6615)
Closes https://github.com/paperless-ngx/paperless-ngx/security/dependabot/181 see https://github.com/advisories/GHSA-wgrm-67xf-hhpq
2024-05-07 16:45:19 +00:00
shamoon
44ed78b442 Documentation: correct display of PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING (#6616) 2024-05-07 09:36:44 -07:00
shamoon
3bd6a6fcfa Update frontend translation strings 2024-05-07 09:11:11 -07:00
shamoon
4fa08a9c96 Change: allow modifier+arrow keyboard shortcuts in input fields 2024-05-07 09:10:38 -07:00
shamoon
8ea3259fe7 Fix: table view doesnt immediately display custom fields on app startup (#6600) 2024-05-07 15:30:34 +00:00
shamoon
fae2399e46 Fix: dont use limit in subqueries for mariadb compatibility (#6611) 2024-05-07 15:14:00 +00:00
shamoon
0d49314593 Fix: exclude admin perms from group permissions serializer (#6608) 2024-05-07 14:39:31 +00:00
shamoon
fda4742e86 Fix: global search text illegible in light mode (#6602) 2024-05-07 01:48:47 -07:00
shamoon
190b648c72 Fix: document history text color illegible in light mode (#6601) 2024-05-07 01:37:13 -07:00
shamoon
22e88046bc Reset dev version string 2024-05-06 23:50:52 -07:00
github-actions[bot]
06447c72c5 Documentation: Add v2.8.1 changelog (#6595)
* Changelog v2.8.1 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-05-06 23:49:53 -07:00
shamoon
7a6fe2da7c Update environment.prod.ts 2024-05-06 23:34:21 -07:00
shamoon
f04cf1a974 Fix: saved views dont immediately display custom fields in table view (#6594) 2024-05-06 23:30:06 -07:00
dependabot[bot]
1ebce6f3e0 Chore(deps-dev): Bump jinja2 from 3.1.3 to 3.1.4 (#6579)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4.
- [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.3...3.1.4)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 06:00:47 +00:00
dependabot[bot]
e07777e38a Chore(deps-dev): Bump mkdocs-glightbox in the small-changes group (#6581)
Bumps the small-changes group with 1 update: [mkdocs-glightbox](https://github.com/Blueswen/mkdocs-glightbox).


Updates `mkdocs-glightbox` from 0.3.7 to 0.4.0
- [Release notes](https://github.com/Blueswen/mkdocs-glightbox/releases)
- [Changelog](https://github.com/blueswen/mkdocs-glightbox/blob/main/CHANGELOG)
- [Commits](https://github.com/Blueswen/mkdocs-glightbox/compare/v0.3.7...v0.4.0)

---
updated-dependencies:
- dependency-name: mkdocs-glightbox
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 05:50:54 +00:00
shamoon
2c69d0fd2e Reset dev version string 2024-05-06 22:34:40 -07:00
github-actions[bot]
b58c114a76 Changelog v2.8.1 - GHA (#6590)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-05-06 22:34:06 -07:00
github-actions[bot]
9aee6f5a78 Documentation: Add v2.8.0 changelog (#6585)
* Changelog v2.8.0 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-05-06 22:19:01 -07:00
shamoon
54edad29ba Bump version to v2.8.1 2024-05-06 22:15:18 -07:00
shamoon
0bf711259a Merge branch 'dev' 2024-05-06 22:14:41 -07:00
github-actions[bot]
1b6250ae24 New Crowdin translations by GitHub Action (#6587)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-05-06 22:13:22 -07:00
shamoon
f2b3521e6c Fix: bulk edit custom fields should support multiple items (#6589) 2024-05-06 22:07:55 -07:00
shamoon
c2944402fa Reset dev version string 2024-05-06 14:50:39 -07:00
shamoon
a0f1f6faa1 Merge branch 'dev' 2024-05-06 14:36:39 -07:00
github-actions[bot]
eaec0014c5 New Crowdin translations by GitHub Action (#6582)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-05-06 14:27:55 -07:00
shamoon
571f3444d1 Merge branch 'dev' 2024-05-06 14:06:20 -07:00
shamoon
57032e234c New Crowdin translations by GitHub Action (#6578)
Co-Authored-By: Crowdin Bot <support+bot@crowdin.com>
2024-05-06 14:05:46 -07:00
shamoon
321adaeb8b Bump version to 2.8.0 2024-05-06 13:41:13 -07:00
shamoon
5d937cf639 Merge branch 'dev' 2024-05-06 13:41:08 -07:00
github-actions[bot]
3e7656e1e1 New Crowdin translations by GitHub Action (#6331)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-05-06 20:40:33 +00:00
Lino
93555cf2e7 Documentation: add link to list of hosting providers (#6577) 2024-05-06 20:33:13 +00:00
shamoon
f60c201eb9 Update README.md 2024-05-06 13:18:16 -07:00
shamoon
78af59ec17 Fix: correct admin permissions check for system status 2024-05-06 12:58:57 -07:00
shamoon
b305372ed1 Updated affiliated projects to related projects 2024-05-05 16:01:32 -07:00
Daniel Rheinbay
16b8b58533 Documentation: fix PAPERLESS_APP_TITLE config type (#6568) 2024-05-05 00:04:44 -07:00
Trenton H
5802163a0e Chore(deps): Bump all allowed backend packages (#6562) 2024-05-04 13:26:15 -07:00
shamoon
b403b9d9d5 Fix ngbDropdown stealing keyboard events 2024-05-04 09:47:27 -07:00
shamoon
c6e7d06bb7 Feature: global search, keyboard shortcuts / hotkey support (#6449) 2024-05-02 16:15:56 +00:00
dependabot[bot]
40289cd714 Chore(deps): Bump stumpylog/image-cleaner-action in the actions group (#6541)
Bumps the actions group with 1 update: [stumpylog/image-cleaner-action](https://github.com/stumpylog/image-cleaner-action).


Updates `stumpylog/image-cleaner-action` from 0.5.0 to 0.6.0
- [Release notes](https://github.com/stumpylog/image-cleaner-action/releases)
- [Changelog](https://github.com/stumpylog/image-cleaner-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/image-cleaner-action/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: stumpylog/image-cleaner-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 15:20:57 +00:00
shamoon
2de9d1b7ae Enhancement: improve layout for custom fields dropdown (#6362) 2024-05-01 21:57:18 -07:00
dependabot[bot]
39b57f695a Chore(deps-dev): Bump ejs from 3.1.9 to 3.1.10 in /src-ui (#6540)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-01 19:27:19 +00:00
dependabot[bot]
f503cd8758 Chore(deps): Bump the frontend-angular-dependencies group (#6539)
Bumps the frontend-angular-dependencies group in /src-ui with 13 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `17.3.2` | `17.3.6` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.3.2` | `17.3.7` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.3.2` | `17.3.7` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.3.2` | `17.3.7` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.3.2` | `17.3.7` |
| [@angular/localize](https://github.com/angular/angular) | `17.3.2` | `17.3.7` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.3.2` | `17.3.7` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.3.2` | `17.3.7` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.3.2` | `17.3.7` |
| [@angular-builders/jest](https://github.com/just-jeb/angular-builders/tree/HEAD/packages/jest) | `17.0.2` | `17.0.3` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.3.2` | `17.3.6` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.3.2` | `17.3.6` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.3.2` | `17.3.7` |


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

Updates `@angular/common` from 17.3.2 to 17.3.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/17.3.7/packages/common)

Updates `@angular/compiler` from 17.3.2 to 17.3.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/17.3.7/packages/compiler)

Updates `@angular/core` from 17.3.2 to 17.3.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/17.3.7/packages/core)

Updates `@angular/forms` from 17.3.2 to 17.3.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/17.3.7/packages/forms)

Updates `@angular/localize` from 17.3.2 to 17.3.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/17.3.2...17.3.7)

Updates `@angular/platform-browser` from 17.3.2 to 17.3.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/17.3.7/packages/platform-browser)

Updates `@angular/platform-browser-dynamic` from 17.3.2 to 17.3.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/17.3.7/packages/platform-browser-dynamic)

Updates `@angular/router` from 17.3.2 to 17.3.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/17.3.7/packages/router)

Updates `@angular-builders/jest` from 17.0.2 to 17.0.3
- [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@17.0.3/packages/jest)

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

Updates `@angular/cli` from 17.3.2 to 17.3.6
- [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/17.3.2...17.3.6)

Updates `@angular/compiler-cli` from 17.3.2 to 17.3.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/17.3.7/packages/compiler-cli)

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-builders/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  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>
2024-05-01 19:20:06 +00:00
shamoon
8d516c08f0 Fix relative date test that will fail across months 2024-05-01 12:07:27 -07:00
shamoon
8b4fc02955 Hide unusable filters 2024-05-01 12:07:27 -07:00
shamoon
6c24686509 Fix: always refresh document after save 2024-05-01 12:07:27 -07:00
Trenton H
7be7185418 Handcrafts SQL queries a little more to reduce the query count and/or the amount of returned data (#6489) 2024-04-30 07:37:09 -07:00
shamoon
63e1f9f5d3 Feature: custom fields filtering & bulk editing (#6484) 2024-04-26 15:10:03 -07:00
shamoon
bd4476d484 Feature: customizable fields display for documents, saved views & dashboard widgets (#6439) 2024-04-26 06:41:12 -07:00
shamoon
7a0334f353 Fix: cast custom fields values list to list for other DB types
Closes #6482
2024-04-24 01:35:45 -07:00
shamoon
d03e48ea88 Fix missing test import 2024-04-24 00:28:18 -07:00
shamoon
342e6d4679 Fix: include number placeholder in relative date strings 2024-04-24 00:22:08 -07:00
dependabot[bot]
584f1361ad Chore(deps): Bump python-ipware in the major-versions group (#6468)
Bumps the major-versions group with 1 update: [python-ipware](https://github.com/un33k/python-ipware).


Updates `python-ipware` from 2.0.3 to 3.0.0
- [Changelog](https://github.com/un33k/python-ipware/blob/main/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-ipware/compare/v2.0.3...v3.0.0)

---
updated-dependencies:
- dependency-name: python-ipware
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-versions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-23 09:08:46 -07:00
shamoon
05b1ff9738 Feature: document history (audit log UI) (#6388) 2024-04-23 15:16:28 +00:00
dependabot[bot]
d65fcf70f3 Chore(deps-dev): Bump the development group with 2 updates (#6466)
* Chore(deps-dev): Bump the development group with 2 updates

Bumps the development group with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.3.7 to 0.4.1
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.7...v0.4.1)

Updates `mkdocs-material` from 9.5.17 to 9.5.18
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.17...9.5.18)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

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

* Updates the ruff hook version to match

---------

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>
2024-04-23 14:53:56 +00:00
shamoon
a5d3d51cc5 Fix: always check workflow filter_mailrule if set (#6474) 2024-04-23 07:37:14 -07:00
shamoon
f4489ca2e7 Enhancement: larger documents in rotate / split dialogs 2024-04-23 00:49:20 -07:00
Benedikt Schwering
e40893e74f Fix: scroll for large tables (#6460)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-04-21 23:31:33 +00:00
shamoon
d002ae2e05 Fix: monetary field with null values 2024-04-20 21:44:31 -07:00
shamoon
bf430865b4 Fix: check original file for rotate 2024-04-20 20:14:44 -07:00
shamoon
a47d36f5e5 Enhancement: speed up merge document list retrieval 2024-04-19 10:17:17 -07:00
shamoon
4392628bd7 Fix: password reset done template error (#6444) 2024-04-19 09:34:34 -07:00
shamoon
6d25eb26a1 Update translation strings 2024-04-19 01:13:11 -07:00
shamoon
95fd1ae879 Enhancement: refactor monetary field (#6370) 2024-04-19 08:08:46 +00:00
shamoon
78f338484f Enhancement: improve layout, button labels for custom fields dropdown (#6362) 2024-04-19 06:57:17 +00:00
Trenton H
40db1065dc Updates QPDF to 11.9.0 from trixie (#6423) 2024-04-18 22:16:44 +00:00
shamoon
c644e57533 Documentation: correct permissions bulk edit parameter 2024-04-18 02:10:40 -07:00
Trenton H
b720aa3cd1 Chore: Convert the consumer to a plugin (#6361) 2024-04-18 02:59:14 +00:00
dependabot[bot]
e837f1e85b Chore(deps): Bump gunicorn from 21.2.0 to 22.0.0 (#6416)
Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 21.2.0 to 22.0.0.
- [Release notes](https://github.com/benoitc/gunicorn/releases)
- [Commits](https://github.com/benoitc/gunicorn/compare/21.2.0...22.0.0)

---
updated-dependencies:
- dependency-name: gunicorn
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-18 00:38:03 +00:00
dependabot[bot]
ea2012bc81 Chore(deps): Bump the small-changes group with 11 updates (#6405)
* Chore(deps): Bump the small-changes group with 11 updates

Bumps the small-changes group with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [django-filter](https://github.com/carltongibson/django-filter) | `24.1` | `24.2` |
| [djangorestframework](https://github.com/encode/django-rest-framework) | `3.14.0` | `3.15.1` |
| [channels](https://github.com/django/channels) | `4.0.0` | `4.1.0` |
| [filelock](https://github.com/tox-dev/py-filelock) | `3.13.3` | `3.13.4` |
| [imap-tools](https://github.com/ikvk/imap_tools) | `1.5.0` | `1.6.0` |
| [python-ipware](https://github.com/un33k/python-ipware) | `2.0.2` | `2.0.3` |
| [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) | `3.7.0` | `3.8.1` |
| [scikit-learn](https://github.com/scikit-learn/scikit-learn) | `1.4.1.post1` | `1.4.2` |
| [black](https://github.com/psf/black) | `24.3.0` | `24.4.0` |
| [ruff](https://github.com/astral-sh/ruff) | `0.3.5` | `0.3.7` |
| [daphne](https://github.com/django/daphne) | `4.1.0` | `4.1.2` |


Updates `django-filter` from 24.1 to 24.2
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/24.1...24.2)

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

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

Updates `filelock` from 3.13.3 to 3.13.4
- [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.13.3...3.13.4)

Updates `imap-tools` from 1.5.0 to 1.6.0
- [Release notes](https://github.com/ikvk/imap_tools/releases)
- [Changelog](https://github.com/ikvk/imap_tools/blob/master/docs/release_notes.rst)
- [Commits](https://github.com/ikvk/imap_tools/compare/v1.5.0...v1.6.0)

Updates `python-ipware` from 2.0.2 to 2.0.3
- [Changelog](https://github.com/un33k/python-ipware/blob/main/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-ipware/compare/v2.0.2...v2.0.3)

Updates `rapidfuzz` from 3.7.0 to 3.8.1
- [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.7.0...v3.8.1)

Updates `scikit-learn` from 1.4.1.post1 to 1.4.2
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.4.1.post1...1.4.2)

Updates `black` from 24.3.0 to 24.4.0
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.3.0...24.4.0)

Updates `ruff` from 0.3.5 to 0.3.7
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.5...v0.3.7)

Updates `daphne` from 4.1.0 to 4.1.2
- [Changelog](https://github.com/django/daphne/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/daphne/compare/4.1.0...4.1.2)

---
updated-dependencies:
- dependency-name: django-filter
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: djangorestframework
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: channels
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: filelock
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: imap-tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: python-ipware
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: rapidfuzz
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: scikit-learn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: black
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: daphne
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: small-changes
...

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

* Reverts DRF update

* Also bumps the hook versions to match

---------

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>
2024-04-18 00:26:10 +00:00
Dominik Bruhn
8e39315586 Enhancement: Hide columns in document list if user does not have permissions (#6415)
---------

Co-authored-by: Dominik Bruhn <dominik@menlo79.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-04-16 21:41:15 +00:00
shamoon
ea8127202d Documentation: Remove SQLite to Postgres in favor of export/import (#6401) 2024-04-15 11:50:04 -07:00
Benedikt Schwering
f009d9868e Fix: show message on empty group list (#6393) 2024-04-14 17:53:48 +00:00
shamoon
1bbcd0961b Fix select dropdown pixel alignment 2024-04-14 08:55:07 -07:00
shamoon
4fa2b54aed Update frontend translation strings 2024-04-13 20:12:54 -07:00
shamoon
7281c110c6 Fix: exclude admin perms from frontend 2024-04-13 20:12:54 -07:00
shamoon
f812f2af4d Fix: remove admin.logentry perm, use admin (staff) status (#6380) 2024-04-14 00:35:34 +00:00
dependabot[bot]
47b4a602a7 Chore(deps): Bump idna from 3.6 to 3.7 (#6377)
Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-12 07:38:28 -07:00
Péter Szász
21c7675f66 Documentation: typo fixes in the configuration docs 2024-04-11 15:27:39 -07:00
dependabot[bot]
ca73c0d1f3 Chore(deps): Bump tar from 6.2.0 to 6.2.1 in /src-ui (#6373)
Bumps [tar](https://github.com/isaacs/node-tar) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.2.0...v6.2.1)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-11 06:51:53 -07:00
shamoon
7f6a50be5b Fix: dont dismiss active alerts on "dismiss completed" (#6364) 2024-04-10 10:55:37 -07:00
shamoon
10e10f9ff4 Fix: Allow lowercase letters in monetary currency code field (#6359) 2024-04-10 08:27:03 -07:00
Trenton H
95c24a50f7 Fix: Allow negative monetary values with a current code (#6358)
* Updates the currency validation to allow an optional negative

* Update frontend regex

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-04-10 07:33:13 -07:00
Harald
d06faa2fcb Fix: add timezone fallback to install script (#6336) 2024-04-08 17:45:43 -07:00
shamoon
bed66cced0 Reset dev version string 2024-04-07 19:04:16 -07:00
github-actions[bot]
ceaf60e6ad Changelog v2.7.2 - GHA (#6329)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-07 19:03:01 -07:00
shamoon
9885ca5103 Bump version to 2.7.2 2024-04-07 18:44:14 -07:00
shamoon
2f22beaaee Merge branch 'dev' 2024-04-07 18:43:43 -07:00
github-actions[bot]
fb2c6282a4 New Crowdin translations by GitHub Action (#6313)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-04-07 18:43:07 -07:00
shamoon
8c5b5d3948 Update translation strings 2024-04-07 18:36:14 -07:00
shamoon
4e5135fe70 Fix: hide page chooser if not loaded yet 2024-04-07 15:41:45 -07:00
shamoon
579c35a3fe Fix: isFiltered check before filter editor init 2024-04-07 15:38:38 -07:00
shamoon
4aedcb856d Fix navbar-brand width on large screens 2024-04-07 15:21:13 -07:00
shamoon
0b34e70f6c Fix: select dropdown background colors not visible in light mode (#6323) 2024-04-07 21:03:15 +00:00
shamoon
7afc91e7b1 Fix: spacing in reset and incorrect display in saved views (#6324) 2024-04-07 20:50:31 +00:00
shamoon
56b17ce6a2 Documentation: add more info to rotation note 2024-04-07 12:46:55 -07:00
shamoon
954912cac3 Update translation strings 2024-04-07 12:41:08 -07:00
shamoon
e46f6b1156 Fix frontend tests warning 2024-04-07 11:55:27 -07:00
shamoon
1d85caa8d0 Fix: disable invalid create endpoints (#6320) 2024-04-07 18:50:40 +00:00
shamoon
622fcf96a0 Fix: initialize current page to 1 2024-04-07 11:15:49 -07:00
shamoon
654cc05f0e Fix: also clear select all checkbox in tasks view 2024-04-07 10:47:27 -07:00
shamoon
974dd24e69 Fix: dont initialize page numbers, allow split with browser pdf viewer (#6314) 2024-04-07 08:16:33 -07:00
shamoon
fe824e0faa Reset dev version string 2024-04-06 18:19:59 -07:00
github-actions[bot]
377d89ae06 Changelog v2.7.1 - GHA (#6311)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-04-06 18:19:22 -07:00
shamoon
629e24e031 Bump version to 2.7.1 2024-04-06 17:42:39 -07:00
shamoon
38414025c8 Fix: update notification spacing 2024-04-06 17:40:22 -07:00
github-actions[bot]
1dc5b7a707 New Crowdin translations by GitHub Action (#6301)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-04-06 17:35:48 -07:00
shamoon
f076418c50 Reset dev version string 2024-04-06 16:50:18 -07:00
shamoon
bbaad2cdfb Merge branch 'main' into dev 2024-04-06 16:50:11 -07:00
github-actions[bot]
ef01658335 Documentation: Add v2.7.0 changelog (#6293) 2024-04-06 16:49:41 -07:00
shamoon
9f4a6c3b42 Fix: Only disable split button if pages = 1 (#6304) 2024-04-06 23:49:00 +00:00
shamoon
5450bfb67b Fix: Use correct custom field id when splitting (#6303) 2024-04-06 23:39:59 +00:00
Trenton H
ae2b302962 Fix: Rotation fails due to celery chord (#6306)
Use Celery immutable signature so it does not take the result of the header task
2024-04-06 23:26:48 +00:00
shamoon
957691c454 Fix: split user / group objects error (#6302) 2024-04-06 13:51:50 -07:00
shamoon
6b17ba2934 Bump version to 2.7.0 2024-04-05 18:00:05 -07:00
shamoon
4d3616cda9 Merge branch 'dev' 2024-04-05 17:59:32 -07:00
github-actions[bot]
c4a9697e02 New Crowdin translations by GitHub Action (#6127)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-04-05 17:59:03 -07:00
shamoon
c57b7520b9 Update translation strings 2024-04-04 14:32:00 -07:00
dependabot[bot]
46bd09227f Chore(deps): Bump pillow from 10.2.0 to 10.3.0 (#6268)
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.2.0 to 10.3.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.2.0...10.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-04 20:39:08 +00:00
dependabot[bot]
971f92a05c Chore(deps-dev): Bump the development group with 2 updates (#6276)
* Chore(deps-dev): Bump the development group with 2 updates

Bumps the development group with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.3.4 to 0.3.5
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.4...v0.3.5)

Updates `mkdocs-material` from 9.5.15 to 9.5.17
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.15...9.5.17)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

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

* Updates ruff hook version to match

---------

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>
2024-04-04 20:26:40 +00:00
Trenton H
2c43b06910 Chore: Standardize subprocess running and logging (#6275) 2024-04-04 13:11:43 -07:00
shamoon
0f8b2e69c9 Change: enable auditlog by default, fix import / export (#6267) 2024-04-04 18:51:15 +00:00
Trenton H
00b04c2e86 Escape the secret key when writing it to the env file (#6243) 2024-04-02 03:02:27 +00:00
shamoon
b3c66cae06 Fix: hide sidebar labels if group is empty (#6254) 2024-04-01 22:00:51 +00:00
shamoon
6a79d417b4 Fix: management list clear all should clear header checkbox (#6253) 2024-04-01 21:43:40 +00:00
dependabot[bot]
98ef68f720 Chore(deps): Bump the frontend-angular-dependencies group (#6248)
Bumps the frontend-angular-dependencies group in /src-ui with 17 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `17.2.1` | `17.3.2` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.2.3` | `17.3.2` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.2.3` | `17.3.2` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.2.3` | `17.3.2` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.2.3` | `17.3.2` |
| [@angular/localize](https://github.com/angular/angular) | `17.2.3` | `17.3.2` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.2.3` | `17.3.2` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.2.3` | `17.3.2` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.2.3` | `17.3.2` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.2.2` | `17.3.2` |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `17.2.1` | `17.3.0` |
| [@angular-eslint/eslint-plugin](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin) | `17.2.1` | `17.3.0` |
| [@angular-eslint/eslint-plugin-template](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template) | `17.2.1` | `17.3.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `17.2.1` | `17.3.0` |
| [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) | `17.2.1` | `17.3.0` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.2.2` | `17.3.2` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.2.3` | `17.3.2` |


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

Updates `@angular/common` from 17.2.3 to 17.3.2
- [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/17.3.2/packages/common)

Updates `@angular/compiler` from 17.2.3 to 17.3.2
- [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/17.3.2/packages/compiler)

Updates `@angular/core` from 17.2.3 to 17.3.2
- [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/17.3.2/packages/core)

Updates `@angular/forms` from 17.2.3 to 17.3.2
- [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/17.3.2/packages/forms)

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

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

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

Updates `@angular/router` from 17.2.3 to 17.3.2
- [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/17.3.2/packages/router)

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

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

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

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

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

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

Updates `@angular/cli` from 17.2.2 to 17.3.2
- [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/17.2.2...17.3.2)

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

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/builder"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/eslint-plugin-template"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/schematics"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/template-parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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>
2024-04-01 14:36:48 -07:00
dependabot[bot]
ed3b7aa8f2 Chore(deps-dev): Bump @playwright/test from 1.42.0 to 1.42.1 in /src-ui (#6250)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.42.0 to 1.42.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.42.0...v1.42.1)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  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>
2024-04-01 21:16:18 +00:00
dependabot[bot]
e536600052 Chore(deps-dev): Bump @types/node from 20.11.24 to 20.12.2 in /src-ui (#6251)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.24 to 20.12.2.
- [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-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>
2024-04-01 21:05:56 +00:00
dependabot[bot]
bb820a2127 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#6249)
Bumps the frontend-eslint-dependencies group in /src-ui with 2 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser).


Updates `@typescript-eslint/eslint-plugin` from 7.1.0 to 7.4.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/v7.4.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 7.1.0 to 7.4.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/v7.4.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  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>
2024-04-01 13:59:08 -07:00
shamoon
129933ff30 Enhancement: support custom fields in post_document endpoint (#6222) 2024-03-30 13:00:53 -07:00
Elias Probst
41fc11efff Enhancement: add ASN to consume rejection message (#6217) 2024-03-28 19:38:29 -07:00
Sander
a712bc72ca Documentation: document_importer does not directly support zip archives (#6209)
---------

Co-authored-by: Sander <biocoder-xbmc@xs4all.nl>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-03-27 19:53:04 -07:00
dependabot[bot]
fbe7acc6b0 Chore(deps-dev): Bump express from 4.18.3 to 4.19.2 in /src-ui (#6207)
Bumps [express](https://github.com/expressjs/express) from 4.18.3 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.3...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-27 11:43:33 -05:00
Trenton H
c4153b6fbf Locks DRF version to 3.14, updates other dependencies (#6200) 2024-03-27 09:13:02 -07:00
shamoon
1f355a22e0 Documentation update info about logout URL 2024-03-27 00:59:18 -05:00
shamoon
4af8070450 Feature: PDF actions - merge, split & rotate (#6094) 2024-03-25 18:41:24 -07:00
shamoon
d6d0071175 Fix: start-align object names in some UI lists (#6188) 2024-03-25 10:01:54 -07:00
shamoon
ef51633b2c Fix: allow scroll long upload files alerts list (#6184) 2024-03-25 07:53:29 -07:00
shamoon
d4963b9cbe Fix: document_renamer fails with audit_log enabled (#6175) 2024-03-24 07:26:25 -07:00
dependabot[bot]
01dabf7c05 Chore(deps-dev): Bump webpack-dev-middleware in /src-ui (#6161)
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-21 13:25:29 -07:00
shamoon
fc68f79cc8 Enhancement: always place search term first in autocomplete results (#6142) 2024-03-21 19:03:17 +00:00
shamoon
ebe1479503 Fix: catch sessionStorage errors for large documents (#6150) 2024-03-21 06:31:25 -07:00
dependabot[bot]
8c9fe4da06 Chore(deps-dev): Bump the development group with 4 updates (#6131)
* Chore(deps-dev): Bump the development group with 4 updates

Bumps the development group with 4 updates: [black](https://github.com/psf/black), [ruff](https://github.com/astral-sh/ruff), [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `black` from 24.2.0 to 24.3.0
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.2.0...24.3.0)

Updates `ruff` from 0.3.2 to 0.3.3
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.2...v0.3.3)

Updates `pytest-rerunfailures` from 13.0 to 14.0
- [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/13.0...14.0)

Updates `mkdocs-material` from 9.5.13 to 9.5.14
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.13...9.5.14)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: pytest-rerunfailures
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

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

* Also matches pre-commit hook versions to Pipfile

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Trenton Holmes <797416+stumpylog@users.noreply.github.com>
2024-03-21 02:09:37 +00:00
shamoon
b2ef51af55 Reset dev version string 2024-03-17 22:58:35 -07:00
github-actions[bot]
32b35d8e4b Changelog v2.6.3 - GHA (#6125)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-17 21:52:09 -07:00
shamoon
c8bda18cf2 Bump version to 2.6.3 2024-03-17 20:45:09 -07:00
shamoon
0a944975cc Merge branch 'dev' 2024-03-17 20:44:39 -07:00
github-actions[bot]
48eaa31ecf New Crowdin translations by GitHub Action (#6055)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-03-17 20:44:19 -07:00
shamoon
1540e88a06 Documentation: fix small typos in file handling docs 2024-03-17 16:43:48 -07:00
shamoon
b1aa57abcb Fix: workflow action reordering 2024-03-17 15:44:10 -07:00
dependabot[bot]
df359730fe Chore(deps-dev): Bump follow-redirects from 1.15.5 to 1.15.6 in /src-ui (#6120)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.5 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-17 00:01:11 +00:00
shamoon
43ec154bc2 Fix: allow setting allauth ACCOUNT_SESSION_REMEMBER (#6105) 2024-03-14 15:34:23 +00:00
shamoon
2c4a664df4 Change: remove credentials from redis url in system status (#6104) 2024-03-14 08:20:34 -07:00
dependabot[bot]
a196c14a58 Chore(deps-dev): Bump the development group with 3 updates (#6079)
* Chore(deps-dev): Bump the development group with 3 updates

Bumps the development group with 3 updates: [ruff](https://github.com/astral-sh/ruff), [pytest](https://github.com/pytest-dev/pytest) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.3.0 to 0.3.2
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.0...v0.3.2)

Updates `pytest` from 8.0.2 to 8.1.1
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.2...8.1.1)

Updates `mkdocs-material` from 9.5.12 to 9.5.13
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.12...9.5.13)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

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

* Updates pre-commit hook versions and runs it against all files

---------

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>
2024-03-12 07:56:01 -07:00
dependabot[bot]
6f549506d6 Chore(deps): Bump the django group with 1 update (#6080)
Bumps the django group with 1 update: [django-filter](https://github.com/carltongibson/django-filter).


Updates `django-filter` from 23.5 to 24.1
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/23.5...24.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 17:33:50 -07:00
dependabot[bot]
8d463e05ae Chore(deps): Bump the small-changes group with 2 updates (#6081)
Bumps the small-changes group with 2 updates: [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) and [redis](https://github.com/redis/redis-py).


Updates `rapidfuzz` from 3.6.1 to 3.6.2
- [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.6.1...v3.6.2)

Updates `redis` from 5.0.2 to 5.0.3
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.0.2...v5.0.3)

---
updated-dependencies:
- dependency-name: rapidfuzz
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 13:32:28 -07:00
shamoon
373c91911d Documentation: improve permissions docs (#6046) 2024-03-11 08:20:52 -07:00
shamoon
1d3ac99c02 Merge branch 'main' into dev 2024-03-09 15:11:53 -08:00
Matt Patterson
cda4c8f87e Documentation: correct bulk edit documents endpoint (#6057) 2024-03-09 15:11:09 -08:00
shamoon
ef4f589094 Change: dont require empty bulk edit parameters (#6059) 2024-03-09 07:39:35 -08:00
shamoon
3aeb45bf34 Merge branch 'main' into dev 2024-03-08 17:05:33 -08:00
shamoon
b91da77a8a Reset dev version string 2024-03-08 17:04:09 -08:00
github-actions[bot]
33357a3fc2 Documentation: Add v2.6.2 changelog (#6049) 2024-03-08 12:14:25 -08:00
Dimitri
025001499d Fix: missing translation string (#6054)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-03-08 07:55:56 -08:00
shamoon
ccd6ad9936 Bump version to 2.6.2 2024-03-07 20:13:29 -08:00
shamoon
979fcb0570 Merge branch 'dev' 2024-03-07 20:12:51 -08:00
github-actions[bot]
58afec98f1 New Crowdin translations by GitHub Action (#6018)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-03-07 20:12:30 -08:00
shamoon
91434a5c6f Enhancement: move and rename files when storage paths deleted, update file handling docs (#6033) 2024-03-07 20:39:55 +00:00
shamoon
37c4545444 Documentation: tweak PAPERLESS_DISABLE_REGULAR_LOGIN info 2024-03-07 09:13:01 -08:00
shamoon
7b7b257725 Fix: make document count lists for objects permissions-aware (#6019) 2024-03-07 15:15:46 +00:00
shamoon
1eff4b306f Enhancement: better detection of default currency code (#6020) 2024-03-07 15:04:32 +00:00
shamoon
700af8caa2 Fix: custom logo size in Webkit (#6036) 2024-03-06 16:08:44 -08:00
Andrew Berry
90b43e154a Documentation: add additional information about Django admin remaining accessible with PAPERLESS_DISABLE_REGULAR_LOGIN (#5993) 2024-03-06 15:46:14 +00:00
shamoon
a3892302b0 Fix: formatting, tiny large card rounded corners bug 2024-03-05 22:40:52 -08:00
shamoon
d2ee319684 Fix: remove outdated mail rule text re. consumption templates 2024-03-05 16:26:35 -08:00
github-actions[bot]
5a6923a9aa Changelog v2.6.1 - GHA (#6014)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-05 08:42:04 -08:00
shamoon
0a634883a7 Reset dev version string 2024-03-05 08:00:32 -08:00
shamoon
428f9cd761 Bump version to 2.6.1 2024-03-05 07:59:44 -08:00
shamoon
d828c1a2ff Merge branch 'dev' into main 2024-03-05 07:59:20 -08:00
github-actions[bot]
25b49db7c0 New Crowdin translations by GitHub Action (#6011)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-03-05 07:55:47 -08:00
shamoon
55a40708a6 Change: tweaks to system status (#6008) 2024-03-05 15:50:04 +00:00
github-actions[bot]
dae5bca883 Documentation: Add v2.6.0 changelog (#6006)
---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-03-05 07:21:09 -08:00
shamoon
fc74da9b82 Fix: prevent edit button click toggling list selection 2024-03-05 06:20:10 -08:00
shamoon
2b006907d5 Reset dev version string 2024-03-04 20:22:07 -08:00
shamoon
1ba1afdce5 Bump version to 2.6.0 2024-03-04 20:20:46 -08:00
shamoon
a98317c52a Fix: search field clear button padding 2024-03-04 20:13:52 -08:00
github-actions[bot]
ffddd0f323 New Crowdin translations by GitHub Action (#5896)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-03-05 03:14:30 +00:00
Trenton H
4cb2f0acef Rolls up the rest of the random updates that dependabot didn't do for some reason (#6005) 2024-03-05 03:00:18 +00:00
shamoon
98663e902f Update translation strings 2024-03-04 17:00:57 -08:00
shamoon
0f5e935214 Fix: header button wrapping issues on mobile 2024-03-04 17:00:19 -08:00
Trenton H
b9636a3def Feature: Allow user to control PIL image pixel limit (#5997) 2024-03-05 00:19:56 +00:00
shamoon
35574f3b86 Fix: prevent actions button click toggling list selection 2024-03-04 16:06:23 -08:00
shamoon
00a8f0cd6e Enhancement: show ID when editing objects (#6003) 2024-03-04 23:21:48 +00:00
Trenton H
6779042242 Feature: Allow a user to disable the pixel limit for OCR entirely (#5996) 2024-03-04 22:37:36 +00:00
dependabot[bot]
6379e7b54f Chore(deps): Bump the small-changes group with 3 updates (#6001)
Bumps the small-changes group with 3 updates: [python-dateutil](https://github.com/dateutil/dateutil), [python-ipware](https://github.com/un33k/python-ipware) and [redis](https://github.com/redis/redis-py).


Updates `python-dateutil` from 2.8.2 to 2.9.0.post0
- [Release notes](https://github.com/dateutil/dateutil/releases)
- [Changelog](https://github.com/dateutil/dateutil/blob/master/NEWS)
- [Commits](https://github.com/dateutil/dateutil/compare/2.8.2...2.9.0.post0)

Updates `python-ipware` from 2.0.1 to 2.0.2
- [Changelog](https://github.com/un33k/python-ipware/blob/main/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-ipware/compare/v2.0.1...v2.0.2)

Updates `redis` from 5.0.1 to 5.0.2
- [Release notes](https://github.com/redis/redis-py/releases)
- [Changelog](https://github.com/redis/redis-py/blob/master/CHANGES)
- [Commits](https://github.com/redis/redis-py/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: python-dateutil
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: python-ipware
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 22:27:59 +00:00
dependabot[bot]
2fa742c94b Chore(deps-dev): Bump the development group with 2 updates (#5998)
* Chore(deps-dev): Bump the development group with 2 updates

Bumps the development group with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.2.2 to 0.3.0
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.2.2...v0.3.0)

Updates `mkdocs-material` from 9.5.11 to 9.5.12
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.11...9.5.12)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

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

* Updates ruff hook version as well

---------

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>
2024-03-04 22:08:13 +00:00
dependabot[bot]
aa0da2f516 Chore(deps): Bump the django group with 1 update (#6000)
Bumps the django group with 1 update: [django](https://github.com/django/django).


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

---
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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 13:15:03 -08:00
shamoon
f07441a408 Feature: workflow removal action (#5928)
---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-03-04 17:37:42 +00:00
shamoon
f6084acfc8 Feature: system status (#5743) 2024-03-04 09:26:25 -08:00
shamoon
23ceb2a5ec Fix: refactor base path settings, correct logout redirect (#5976) 2024-03-03 08:22:23 -08:00
dependabot[bot]
a698791059 Chore(deps-dev): Bump @playwright/test from 1.41.2 to 1.42.0 in /src-ui (#5964)
* Chore(deps-dev): Bump @playwright/test from 1.41.2 to 1.42.0 in /src-ui

Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.41.2 to 1.42.0.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.41.2...v1.42.0)

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

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

* Revert .har changes from playwright bug

---------

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>
2024-03-01 21:07:08 +00:00
dependabot[bot]
41c1f38ab2 Chore(deps-dev): Bump @types/node from 20.11.20 to 20.11.24 in /src-ui (#5965)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.20 to 20.11.24.
- [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-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>
2024-03-01 21:00:09 +00:00
dependabot[bot]
83c85dc10e Chore(deps): Bump the frontend-angular-dependencies group (#5963)
Bumps the frontend-angular-dependencies group in /src-ui with 11 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.2.2` | `17.2.3` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.2.2` | `17.2.3` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.2.2` | `17.2.3` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.2.2` | `17.2.3` |
| [@angular/localize](https://github.com/angular/angular) | `17.2.2` | `17.2.3` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.2.2` | `17.2.3` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.2.2` | `17.2.3` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.2.2` | `17.2.3` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.2.1` | `17.2.2` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.2.1` | `17.2.2` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.2.2` | `17.2.3` |


Updates `@angular/common` from 17.2.2 to 17.2.3
- [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/17.2.3/packages/common)

Updates `@angular/compiler` from 17.2.2 to 17.2.3
- [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/17.2.3/packages/compiler)

Updates `@angular/core` from 17.2.2 to 17.2.3
- [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/17.2.3/packages/core)

Updates `@angular/forms` from 17.2.2 to 17.2.3
- [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/17.2.3/packages/forms)

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

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

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

Updates `@angular/router` from 17.2.2 to 17.2.3
- [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/17.2.3/packages/router)

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

Updates `@angular/cli` from 17.2.1 to 17.2.2
- [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/17.2.1...17.2.2)

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

---
updated-dependencies:
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  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>
2024-03-01 20:51:12 +00:00
shamoon
a020d807d4 Fix: always pass remove_inbox_tags from UI, dont require in API (#5962) 2024-03-01 12:00:54 -08:00
shamoon
464ee51de8 Fix: mobile sizing tweaks 2024-02-29 16:57:47 -08:00
Trenton H
c57c1d5389 Fix: Clear metadata cache when the filename(s) change (#5957)
* Update doc modified time upon move and rename

* Clear the cached metadata if the filename(s) have been changed

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-02-29 10:34:15 -08:00
shamoon
af16bb3934 Fix: include monetary, float and doc link values in search filters (#5951) 2024-02-29 08:21:56 +00:00
Trenton H
fba416e8e1 Fixes a corrupted index not being handled by whoosh.create_in (#5950) 2024-02-29 00:07:17 +00:00
Trenton H
3d8de50b5a Don't assume the /tmp folder is where the user wants to place the scratch directory (#5948) 2024-02-28 22:46:24 +00:00
Trenton H
86263a52ea Includes OCRMyPdf logging into the log file (#5947) 2024-02-28 14:39:42 -08:00
shamoon
754627681c Fix: ensure document title always limited to 128 chars (#5934) 2024-02-28 07:05:19 -08:00
shamoon
bf11dc8d1b Enhancement: better monetary field with currency code (#5858) 2024-02-27 16:26:06 +00:00
Dennis Gaida
84721b001f Change: add Thumbs.db to default ignores (#5924)
* Adding Thumbs.db to default ignores

Thanks Windows for creating these great little database files everywhere

* Updating configuration with new default
2024-02-27 07:48:50 -08:00
Felix Eckhofer
f48a20c75f Documentation: fix typo (#5922) 2024-02-27 00:25:18 -08:00
shamoon
16f4552e0e Fix: use PAPERLESS_URL if set for pw reset emails (#5902) 2024-02-26 21:41:25 +00:00
ShanSanear
86811d0733 Fix: Correcting check searching for docker compose plugin (#5904) (#5917) 2024-02-26 21:21:43 +00:00
shamoon
d2f9b5d5e5 Fix: respect global permissions for UI settings (#5919) 2024-02-26 20:19:31 +00:00
shamoon
f5e1675107 Fix: tasks badge position on mobile 2024-02-26 12:02:45 -08:00
dependabot[bot]
ae016cae4b Chore(deps-dev): Bump the frontend-eslint-dependencies group (#5918)
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 [eslint](https://github.com/eslint/eslint).


Updates `@typescript-eslint/eslint-plugin` from 6.20.0 to 7.1.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/v7.1.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.20.0 to 7.1.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/v7.1.0/packages/parser)

Updates `eslint` from 8.56.0 to 8.57.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/v8.56.0...v8.57.0)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  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>
2024-02-26 11:22:26 -08:00
dependabot[bot]
c7f6d03508 Chore(deps-dev): Bump @types/node from 20.11.16 to 20.11.20 in /src-ui (#5912)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.11.16 to 20.11.20.
- [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-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>
2024-02-26 18:55:52 +00:00
dependabot[bot]
955f2d0db9 Chore(deps): Bump zone.js from 0.14.3 to 0.14.4 in /src-ui (#5913)
Bumps [zone.js](https://github.com/angular/angular/tree/HEAD/packages/zone.js) from 0.14.3 to 0.14.4.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/packages/zone.js/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/zone.js-0.14.4/packages/zone.js)

---
updated-dependencies:
- dependency-name: zone.js
  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>
2024-02-26 18:30:58 +00:00
dependabot[bot]
ba32684df6 Chore(deps): Bump bootstrap from 5.3.2 to 5.3.3 in /src-ui (#5911)
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.2 to 5.3.3.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.2...v5.3.3)

---
updated-dependencies:
- dependency-name: bootstrap
  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>
2024-02-26 18:20:03 +00:00
dependabot[bot]
2f7adf40ac Chore(deps-dev): Bump typescript from 5.2.2 to 5.3.3 in /src-ui (#5915)
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 5.2.2 to 5.3.3.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v5.2.2...v5.3.3)

---
updated-dependencies:
- dependency-name: typescript
  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>
2024-02-26 18:08:50 +00:00
dependabot[bot]
a52031161b Chore(deps): Bump the frontend-angular-dependencies group (#5908)
Bumps the frontend-angular-dependencies group in /src-ui with 15 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `17.1.2` | `17.2.1` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.1.2` | `17.2.2` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.1.2` | `17.2.2` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.1.2` | `17.2.2` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.1.2` | `17.2.2` |
| [@angular/localize](https://github.com/angular/angular) | `17.1.2` | `17.2.2` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.1.2` | `17.2.2` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.1.2` | `17.2.2` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.1.2` | `17.2.2` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `12.0.6` | `12.0.7` |
| [ngx-cookie-service](https://github.com/stevermeister/ngx-cookie-service) | `17.0.1` | `17.1.0` |
| [@angular-builders/jest](https://github.com/just-jeb/angular-builders/tree/HEAD/packages/jest) | `17.0.0` | `17.0.2` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.2.0` | `17.2.1` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.1.2` | `17.2.1` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.1.2` | `17.2.2` |


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

Updates `@angular/common` from 17.1.2 to 17.2.2
- [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/17.2.2/packages/common)

Updates `@angular/compiler` from 17.1.2 to 17.2.2
- [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/17.2.2/packages/compiler)

Updates `@angular/core` from 17.1.2 to 17.2.2
- [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/17.2.2/packages/core)

Updates `@angular/forms` from 17.1.2 to 17.2.2
- [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/17.2.2/packages/forms)

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

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

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

Updates `@angular/router` from 17.1.2 to 17.2.2
- [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/17.2.2/packages/router)

Updates `@ng-select/ng-select` from 12.0.6 to 12.0.7
- [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/v12.0.6...v12.0.7)

Updates `ngx-cookie-service` from 17.0.1 to 17.1.0
- [Release notes](https://github.com/stevermeister/ngx-cookie-service/releases)
- [Changelog](https://github.com/stevermeister/ngx-cookie-service/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stevermeister/ngx-cookie-service/compare/v17.0.1...v17.1.0)

Updates `@angular-builders/jest` from 17.0.0 to 17.0.2
- [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@17.0.2/packages/jest)

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

Updates `@angular/cli` from 17.1.2 to 17.2.1
- [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/17.1.2...17.2.1)

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

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@ng-select/ng-select"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: ngx-cookie-service
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-builders/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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>
2024-02-26 17:58:26 +00:00
shamoon
3e2c541b7b Fix: allow disable allauth email verification (#5895) 2024-02-26 09:33:59 -08:00
shamoon
967fc98090 Fix: Refactor accounts templates and create signup template (#5899) 2024-02-26 09:23:53 -08:00
dependabot[bot]
22e95f45bd Chore(deps): Bump the small-changes group with 4 updates (#5916)
Bumps the small-changes group with 4 updates: [scikit-learn](https://github.com/scikit-learn/scikit-learn), [tqdm](https://github.com/tqdm/tqdm), [pre-commit](https://github.com/pre-commit/pre-commit) and [daphne](https://github.com/django/daphne).


Updates `scikit-learn` from 1.4.0 to 1.4.1.post1
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.4.0...1.4.1.post1)

Updates `tqdm` from 4.66.1 to 4.66.2
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.1...v4.66.2)

Updates `pre-commit` from 3.6.0 to 3.6.2
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.6.0...v3.6.2)

Updates `daphne` from 4.0.0 to 4.1.0
- [Changelog](https://github.com/django/daphne/blob/main/CHANGELOG.txt)
- [Commits](https://github.com/django/daphne/commits)

---
updated-dependencies:
- dependency-name: scikit-learn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: tqdm
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: pre-commit
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: daphne
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 09:11:59 -08:00
dependabot[bot]
38c777ec0f Chore(deps-dev): Bump the development group with 4 updates (#5914)
* Chore(deps-dev): Bump the development group with 4 updates

Bumps the development group with 4 updates: [ruff](https://github.com/astral-sh/ruff), [pytest](https://github.com/pytest-dev/pytest), [pytest-httpx](https://github.com/Colin-b/pytest_httpx) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.2.1 to 0.2.2
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.2.1...v0.2.2)

Updates `pytest` from 8.0.0 to 8.0.2
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.0...8.0.2)

Updates `pytest-httpx` from 0.29.0 to 0.30.0
- [Release notes](https://github.com/Colin-b/pytest_httpx/releases)
- [Changelog](https://github.com/Colin-b/pytest_httpx/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/Colin-b/pytest_httpx/compare/v0.29.0...v0.30.0)

Updates `mkdocs-material` from 9.5.9 to 9.5.11
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.9...9.5.11)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: pytest-httpx
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

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

* Updates the hook versions to match installed versions

---------

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>
2024-02-26 16:27:45 +00:00
dependabot[bot]
ccbc97399a Chore(deps): Bump the actions group with 3 updates (#5907)
Bumps the actions group with 3 updates: [pre-commit/action](https://github.com/pre-commit/action), [release-drafter/release-drafter](https://github.com/release-drafter/release-drafter) and [stumpylog/image-cleaner-action](https://github.com/stumpylog/image-cleaner-action).


Updates `pre-commit/action` from 3.0.0 to 3.0.1
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1)

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

Updates `stumpylog/image-cleaner-action` from 0.4.0 to 0.5.0
- [Release notes](https://github.com/stumpylog/image-cleaner-action/releases)
- [Changelog](https://github.com/stumpylog/image-cleaner-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/image-cleaner-action/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: pre-commit/action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: release-drafter/release-drafter
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: stumpylog/image-cleaner-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 08:08:15 -08:00
Trenton H
f43013a746 Ignores uvicorn updates in dependabot (#5906) 2024-02-26 07:55:59 -08:00
shamoon
1335ab5f1b Enhancement: support disabling regular login (#5816) 2024-02-26 05:17:21 +00:00
shamoon
90b4691f16 Reset dev version string 2024-02-25 19:43:25 -08:00
github-actions[bot]
f053ee3191 Changelog v2.5.4 - GHA (#5894)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-25 19:33:07 -08:00
shamoon
86748c1e96 Merge branch 'dev' 2024-02-25 18:41:01 -08:00
github-actions[bot]
22ded7d4c3 New Crowdin translations by GitHub Action (#5805)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-02-25 18:39:05 -08:00
shamoon
ff5063849a Bump version to 2.5.4 2024-02-25 18:38:12 -08:00
shamoon
ec49284274 Merge branch 'dev' 2024-02-25 18:37:19 -08:00
shamoon
6bd5c34b54 Fix: handle title placeholder for docs without original_filename (#5828) 2024-02-25 17:45:35 -08:00
shamoon
db0a2eb1a3 Fix: bulk edit objects does not respect global permissions (#5888) 2024-02-25 16:59:59 -08:00
shamoon
4948438378 Fix: intermittent save & close warnings in v2.5.3 (#5838) 2024-02-26 00:59:29 +00:00
grembo
76064178f5 Fix: inotify read timeout not in ms (#5876)
* Fix inotify read timeout

This was off by a factor of 1000, leading to a lot more invocations
of the loop body than necessary.

* Incorporate review feedback
2024-02-26 00:46:47 +00:00
noantiq
c772bd94b0 Documentation: fix links to django-allauth (#5893) 2024-02-25 14:09:44 -08:00
shamoon
7f8f7fbb15 Fix: logo spacing tweaks 2024-02-24 11:14:27 -08:00
shamoon
388d821f45 Fix: only require DjangoModelPermissions for app config 2024-02-24 10:53:09 -08:00
shamoon
9c15623a89 Change: increase dark mode selected dropdown item bg contrast 2024-02-24 09:59:20 -08:00
shamoon
966eb00de0 Chore: miscellaneous frontend testing 2024-02-23 23:36:03 -08:00
Nepomacs
1c699278a3 Documentation: note required trailing slash for REST-API URLs (#5863)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-02-22 07:22:33 -08:00
shamoon
4c6c976f63 Chore: better explain repository maintenance (#5815) 2024-02-21 18:52:55 -08:00
shamoon
ebc9ce17b5 Fix: accidentally retained confirm button for management lists 2024-02-21 15:27:15 -08:00
dependabot[bot]
8039ce3c2b Chore(deps): Bump cryptography from 42.0.2 to 42.0.4 (#5851)
Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.2 to 42.0.4.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.2...42.0.4)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-21 20:55:18 +00:00
shamoon
385d48f644 Fix workflow migration model imports 2024-02-21 12:32:58 -08:00
dependabot[bot]
f682fe25fc Chore(deps-dev): Bump ip from 2.0.0 to 2.0.1 in /src-ui (#5835)
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-20 20:24:38 +00:00
shamoon
7924bf8611 Chore: close unsupported FRs (#5804) 2024-02-18 08:01:08 -08:00
dependabot[bot]
d75b909d28 Chore(deps): Bump undici and @angular-devkit/build-angular in /src-ui (#5796)
Bumps [undici](https://github.com/nodejs/undici) to 6.6.2 and updates ancestor dependency [@angular-devkit/build-angular](https://github.com/angular/angular-cli). These dependencies need to be updated together.


Updates `undici` from 6.2.1 to 6.6.2
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.2.1...v6.6.2)

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

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 06:45:41 +00:00
shamoon
47dfe85a7c Fix: allow relative date queries not in quick list (#5801) 2024-02-16 22:36:45 -08:00
shamoon
4d0e8a338f Fix: Pass rule id to consumed .eml files (#5800) 2024-02-16 22:36:33 -08:00
shamoon
cfc64d37bb Create SECURITY.md 2024-02-15 23:38:33 -08:00
github-actions[bot]
2db66280cc Documentation: Add v2.5.3 changelog (#5786)
* Changelog v2.5.3 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-02-15 18:20:26 -08:00
shamoon
9f045f4494 Bump version to 2.5.3 2024-02-15 17:34:22 -08:00
shamoon
4fdb28c8d6 Merge branch 'dev' 2024-02-15 17:33:54 -08:00
shamoon
f1049cf889 Fix: dont allow allauth redirects to any host (#5783)
---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-02-15 16:37:34 -08:00
Trenton H
8d664fad56 Fixes the interaction when both splitting and ASN are enabled (#5779) 2024-02-15 17:33:26 +00:00
MaciejSzczurek
f6ddcfa839 Moved ssl_mode parameter for mysql backend engine (#5771) 2024-02-15 07:59:33 -08:00
github-actions[bot]
ce59f2ad5e Changelog v2.5.2 - GHA (#5769)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-14 17:40:10 -08:00
Trenton H
0de00a4ac1 Resets develop versioning 2024-02-14 17:14:33 -08:00
Trenton H
bec72dffeb Sets the version to 2.5.2 2024-02-14 17:10:37 -08:00
Trenton H
463e95367c Merge remote-tracking branch 'origin/dev' 2024-02-14 17:08:49 -08:00
github-actions[bot]
1739de2694 New Crowdin translations by GitHub Action (#5768)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-02-14 16:53:28 -08:00
shamoon
fd8db27a88 Fix signup form html, translation string 2024-02-14 16:51:47 -08:00
github-actions[bot]
9ddf14bebe New Crowdin translations by GitHub Action (#5748)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-02-14 16:24:24 -08:00
trs
1cf8ea3aba Fix: Generated secret key may include single or double quotes (#5767) 2024-02-14 23:06:54 +00:00
shamoon
134993fce6 Fix: consumer status alerts container blocks elements (#5762) 2024-02-14 08:25:58 -08:00
shamoon
ff1955e014 Documentation: small updates to sso docs 2024-02-14 00:02:55 -08:00
shamoon
d83bbdc50b Fix: handle document notes user format api change (#5751) 2024-02-13 17:25:35 +00:00
Trenton H
907b6d1294 Handle the ASN assignment last, after the splitting (#5745) 2024-02-13 16:26:13 +00:00
github-actions[bot]
4e7bb1c8da Changelog v2.5.1 - GHA (#5744)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-13 06:07:31 -08:00
dependabot[bot]
09ab694d05 Chore(deps): Bump the major-versions group with 1 update (#5741)
Bumps the major-versions group with 1 update: [watchdog](https://github.com/gorakhargosh/watchdog).


Updates `watchdog` from 3.0.0 to 4.0.0
- [Release notes](https://github.com/gorakhargosh/watchdog/releases)
- [Changelog](https://github.com/gorakhargosh/watchdog/blob/master/changelog.rst)
- [Commits](https://github.com/gorakhargosh/watchdog/compare/v3.0.0...v4.0.0)

---
updated-dependencies:
- dependency-name: watchdog
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major-versions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-13 09:30:55 +00:00
Trenton H
03ced65d5f Resets version string 2024-02-12 15:58:10 -08:00
Trenton H
01d919cf31 Bumps version to 2.5.1 2024-02-12 15:56:41 -08:00
Trenton H
8d5f331e63 Merge remote-tracking branch 'origin/dev' 2024-02-12 15:56:19 -08:00
github-actions[bot]
ed556ead6f New Crowdin translations by GitHub Action (#5714)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-02-12 23:50:25 +00:00
dependabot[bot]
e10a904f33 Chore(deps-dev): Bump the development group with 2 updates (#5737)
Bumps the development group with 2 updates: [black](https://github.com/psf/black) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `black` from 24.1.1 to 24.2.0
- [Release notes](https://github.com/psf/black/releases)
- [Changelog](https://github.com/psf/black/blob/main/CHANGES.md)
- [Commits](https://github.com/psf/black/compare/24.1.1...24.2.0)

Updates `mkdocs-material` from 9.5.8 to 9.5.9
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.8...9.5.9)

---
updated-dependencies:
- dependency-name: black
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-12 13:54:31 -08:00
dependabot[bot]
f2a05b61da Chore(deps): Bump the django group with 1 update (#5739)
Bumps the django group with 1 update: [django-allauth](https://github.com/pennersr/django-allauth).


Updates `django-allauth` from 0.61.0 to 0.61.1
- [Changelog](https://github.com/pennersr/django-allauth/blob/main/ChangeLog.rst)
- [Commits](https://github.com/pennersr/django-allauth/compare/0.61.0...0.61.1)

---
updated-dependencies:
- dependency-name: django-allauth
  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>
2024-02-12 21:05:34 +00:00
Trenton H
21f96f0679 Fix: Splitting on ASN barcodes even if not enabled (#5740)
* Fixes the barcodes always splitting on ASNs, even if splitting was disabled
2024-02-12 12:58:37 -08:00
shamoon
2ffabd54e5 Documentation: add barcode tagging info 2024-02-12 12:51:52 -08:00
Dennis Gaida
1197437750 Documentation: explain PAPERLESS_APPS for external authentication (#5730) 2024-02-12 07:10:14 -08:00
shamoon
d1339374d0 Fix: add missing icon 2024-02-10 22:42:02 -08:00
Trenton Holmes
5ba4b9d6b2 Corrects the spelling 2024-02-10 18:53:03 -08:00
github-actions[bot]
b386ea9426 Changelog v2.5.0 - GHA (#5715)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-02-10 18:46:53 -08:00
Trenton H
197174f400 Resets -dev version string 2024-02-10 11:46:51 -08:00
Trenton H
97dceba783 Actual 2.5.0 release now 2024-02-10 11:45:46 -08:00
github-actions[bot]
2ed4400827 New Crowdin translations by GitHub Action (#5590)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-02-10 19:43:30 +00:00
Trenton H
58cbcbd6ef Resets -dev version string 2024-02-10 11:15:12 -08:00
Trenton H
4aeb2e1a74 Bumps version to 2.5.0 2024-02-10 11:14:16 -08:00
Trenton H
45c5f81b34 Merge remote-tracking branch 'origin/dev' 2024-02-10 11:12:27 -08:00
Trenton H
13201dbfff Ensure all creations of directories create the parents too (#5711) 2024-02-10 11:02:40 -08:00
Trenton H
0b1523f4e5 Fix: Test metadata items for Unicode issues (#5707)
Test each key for unicode issues and reject ones which will fail inside DRF
2024-02-09 20:08:23 +00:00
shamoon
cd3b1a221e Change: show preview even if metadata fails (#5706) 2024-02-09 11:35:32 -08:00
shamoon
4855f4b8b1 Fix: only check workflow trigger source if not empty (#5701) 2024-02-09 08:57:09 -08:00
shamoon
6587470033 Update translation strings 2024-02-08 11:56:55 -08:00
shamoon
6487dab132 Enhancement: confirm buttons (#5680) 2024-02-08 18:23:06 +00:00
shamoon
b643a68fa3 Enhancement: bulk delete objects (#5688) 2024-02-08 10:13:15 -08:00
Trenton H
b60e16fe33 Chore: Backend dependencies update (#5676) 2024-02-08 09:48:24 -08:00
Moritz Pflanzer
c508be6ecd Feature: OIDC & social authentication (#5190)
---------

Co-authored-by: Moritz Pflanzer <moritz@chickadee-engineering.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-02-08 16:15:38 +00:00
silmaril42
3b2d4fe876 Documentation: add detail about consumer polling behavior (#5674) 2024-02-08 02:46:25 +00:00
Trenton H
b47f301831 Adds the Python environment flags for no byte code saving, reducing the image size slightly (#5677) 2024-02-08 02:45:59 +00:00
shamoon
a79b9de1a2 Documentation: Add storage path to workflow action assignments 2024-02-07 09:46:20 -08:00
shamoon
e98da2e72c Fix whitespace in some frontend html files 2024-02-07 07:12:24 -08:00
shamoon
718171a125 Resolve svg rem width/height attribute warning 2024-02-06 14:50:08 -08:00
shamoon
aaa130e20d Feature: allow create objects from bulk edit (#5667) 2024-02-06 15:31:07 +00:00
shamoon
4606caeaa8 Chore: Use memory cache backend in debug mode (#5666) 2024-02-06 07:16:15 -08:00
Trenton H
4813a7bc70 Chore: Adds additional rules for Ruff linter (#5660) 2024-02-05 21:46:59 +00:00
pkrahmer
fb82aa0ee1 Feature: Allow tagging by putting barcode stickers on documents (#5580) 2024-02-05 17:38:19 +00:00
LarsBel
c7e0c32226 Documentation: Include example redis URI with database index (#5653)
Co-Authored-By: shamoon <4887959+shamoon@users.noreply.github.com>
2024-02-04 15:46:43 -08:00
shamoon
607adf44f3 Documentation: Make remote-user warning clearer, maybe 2024-02-04 14:20:48 -08:00
shamoon
625780899d Fix location of bulk edit tests 2024-02-04 12:39:26 -08:00
Trenton H
25542c56b9 Feature: Cache metadata and suggestions in Redis (#5638) 2024-02-04 10:42:21 -08:00
shamoon
45e2b7f814 Fix: frontend validation of number fields fails upon save (#5646) 2024-02-03 21:22:12 +00:00
shamoon
6b34f592df Fix: Explicit validation of custom field name unique constraint (#5647) 2024-02-03 20:51:26 +00:00
shamoon
6cf732e6ec Add Japanese translation (#5641) 2024-02-03 11:37:21 -08:00
shamoon
dfd959839f Update translation strings 2024-02-02 20:16:14 -08:00
shamoon
d165e89ac3 Merge branch 'main' into dev 2024-02-02 07:44:51 -08:00
shamoon
421a87c94b Add DO demo info (#5639) 2024-02-02 07:39:06 -08:00
dependabot[bot]
b55529b913 Chore(deps-dev): Bump @playwright/test from 1.40.1 to 1.41.2 in /src-ui (#5634)
* Chore(deps-dev): Bump @playwright/test from 1.40.1 to 1.41.2 in /src-ui

Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.40.1 to 1.41.2.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.40.1...v1.41.2)

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

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

* Update tests for breaking playwright change

---------

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>
2024-02-01 22:06:10 -08:00
shamoon
c62d892969 Feature: option for auto-remove inbox tags on save (#5562) 2024-02-02 02:41:10 +00:00
dependabot[bot]
9e6aa55230 Chore(deps): Bump the frontend-angular-dependencies group (#5630)
Bumps the frontend-angular-dependencies group in /src-ui with 19 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `17.0.4` | `17.1.2` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.0.8` | `17.1.2` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.0.8` | `17.1.2` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.0.8` | `17.1.2` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.0.8` | `17.1.2` |
| [@angular/localize](https://github.com/angular/angular) | `17.0.8` | `17.1.2` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.0.8` | `17.1.2` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.0.8` | `17.1.2` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.0.8` | `17.1.2` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `12.0.4` | `12.0.6` |
| [ngx-ui-tour-ng-bootstrap](https://github.com/hakimio/ngx-ui-tour) | `14.0.1` | `14.0.2` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.0.8` | `17.1.2` |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `17.1.1` | `17.2.1` |
| [@angular-eslint/eslint-plugin](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin) | `17.1.1` | `17.2.1` |
| [@angular-eslint/eslint-plugin-template](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template) | `17.1.1` | `17.2.1` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `17.1.1` | `17.2.1` |
| [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) | `17.1.1` | `17.2.1` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.0.8` | `17.1.2` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `17.0.8` | `17.1.2` |


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

Updates `@angular/common` from 17.0.8 to 17.1.2
- [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/17.1.2/packages/common)

Updates `@angular/compiler` from 17.0.8 to 17.1.2
- [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/17.1.2/packages/compiler)

Updates `@angular/core` from 17.0.8 to 17.1.2
- [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/17.1.2/packages/core)

Updates `@angular/forms` from 17.0.8 to 17.1.2
- [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/17.1.2/packages/forms)

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

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

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

Updates `@angular/router` from 17.0.8 to 17.1.2
- [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/17.1.2/packages/router)

Updates `@ng-select/ng-select` from 12.0.4 to 12.0.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/v12.0.4...v12.0.6)

Updates `ngx-ui-tour-ng-bootstrap` from 14.0.1 to 14.0.2
- [Release notes](https://github.com/hakimio/ngx-ui-tour/releases)
- [Commits](https://github.com/hakimio/ngx-ui-tour/commits)

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

Updates `@angular-eslint/builder` from 17.1.1 to 17.2.1
- [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/v17.2.1/packages/builder)

Updates `@angular-eslint/eslint-plugin` from 17.1.1 to 17.2.1
- [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/v17.2.1/packages/eslint-plugin)

Updates `@angular-eslint/eslint-plugin-template` from 17.1.1 to 17.2.1
- [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/v17.2.1/packages/eslint-plugin-template)

Updates `@angular-eslint/schematics` from 17.1.1 to 17.2.1
- [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/v17.2.1/packages/schematics)

Updates `@angular-eslint/template-parser` from 17.1.1 to 17.2.1
- [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/v17.2.1/packages/template-parser)

Updates `@angular/cli` from 17.0.8 to 17.1.2
- [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/17.0.8...17.1.2)

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

---
updated-dependencies:
- dependency-name: "@angular/cdk"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@ng-select/ng-select"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: ngx-ui-tour-ng-bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/builder"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/eslint-plugin-template"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/schematics"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-eslint/template-parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler-cli"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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>
2024-02-01 14:49:49 -08:00
dependabot[bot]
6090305b77 Chore(deps-dev): Bump the frontend-jest-dependencies group (#5631)
Bumps the frontend-jest-dependencies group in /src-ui with 2 updates: [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) and [jest-preset-angular](https://github.com/thymikee/jest-preset-angular).


Updates `@types/jest` from 29.5.11 to 29.5.12
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Updates `jest-preset-angular` from 13.1.4 to 14.0.0
- [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/v13.1.4...v14.0.0)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-jest-dependencies
- dependency-name: jest-preset-angular
  dependency-type: direct:development
  update-type: version-update:semver-major
  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>
2024-02-01 21:43:36 +00:00
dependabot[bot]
d1b516a089 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#5632)
Bumps the frontend-eslint-dependencies group in /src-ui with 2 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser).


Updates `@typescript-eslint/eslint-plugin` from 6.17.0 to 6.20.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/v6.20.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.17.0 to 6.20.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/v6.20.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  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>
2024-02-01 21:36:09 +00:00
dependabot[bot]
89aff63e52 Chore(deps): Bump zone.js from 0.14.2 to 0.14.3 in /src-ui (#5633)
Bumps [zone.js](https://github.com/angular/angular/tree/HEAD/packages/zone.js) from 0.14.2 to 0.14.3.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/packages/zone.js/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/zone.js-0.14.3/packages/zone.js)

---
updated-dependencies:
- dependency-name: zone.js
  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>
2024-02-01 21:26:21 +00:00
dependabot[bot]
11e9c4d8cc Chore(deps-dev): Bump @types/node from 20.10.6 to 20.11.16 in /src-ui (#5635)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.10.6 to 20.11.16.
- [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-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>
2024-02-01 21:18:29 +00:00
Henrik Gerdes
9d84e95771 feat: add env allowing paperless to run in read_only filesystem (#5596) 2024-02-01 20:42:23 +00:00
dependabot[bot]
2aced1c305 Chore(deps): Bump the actions group with 1 update (#5629)
Bumps the actions group with 1 update: [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `codecov/codecov-action` from 3 to 4
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  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>
2024-02-01 12:29:38 -08:00
shamoon
454098630b Enhancement: mergeable bulk edit permissions (#5508) 2024-02-01 19:56:57 +00:00
이토비
8f3ab2791b Documentation: fix more like parameter in API docs (#5624) 2024-02-01 11:49:34 -08:00
shamoon
61209b1057 Enhancement: re-implement remote user auth for API as opt-in (#5561) 2024-02-01 19:46:26 +00:00
Henning B
38a817e887 Enhancement: Respect PDF cropbox for thumbnail generation (#5531)
* Use the convert argument '-define "pdf:use-cropbox=true"' when creating thumbnails. That way cropboxes, if present, gets respected for thumbnail generation
2024-02-01 11:25:57 -08:00
shamoon
5e3d1b26e7 Fix: Dont attempt to retrieve objects for which user doesnt have global permissions (#5612) 2024-02-01 01:20:14 -08:00
shamoon
4996b7e5f7 Correct position of text filter clear button 2024-01-30 22:06:22 -08:00
dependabot[bot]
2c8fddb554 Chore(deps): Bump the actions group with 1 update (#5597)
Bumps the actions group with 1 update: [actions/cache](https://github.com/actions/cache).


Updates `actions/cache` from 3 to 4
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  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>
2024-01-30 00:10:12 +00:00
Michael Wieland
ae05011062 Documentation: Add docs about missing timezones in MySQL/MariaDB (#5583)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-29 19:07:40 +00:00
github-actions[bot]
50a6b7e154 Documentation: Add v2.4.3 changelog (#5584)
* Changelog v2.4.3 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-28 15:14:43 -08:00
Trenton H
d0ce4113e0 Resets -dev version string 2024-01-28 12:51:00 -08:00
Trenton H
d55900b877 Bumps version to 2.4.3 2024-01-28 12:19:07 -08:00
github-actions[bot]
2a73ab4693 New Crowdin translations by GitHub Action (#5559)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-28 20:16:42 +00:00
Trenton H
2aea220c6d Ensure the scratch directory exists before consuming this source (#5579) 2024-01-28 09:07:04 -08:00
shamoon
b0c305e852 Merge branch 'main' into dev 2024-01-26 12:47:00 -08:00
shamoon
73a77d2a45 Remove demo info, for now (#5557) 2024-01-26 12:46:22 -08:00
shamoon
3a011e7c04 Reset dev version string 2024-01-26 09:28:04 -08:00
github-actions[bot]
d48b75d862 Changelog v2.4.2 - GHA (#5548)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-25 16:09:13 -08:00
shamoon
f6e26d5953 Bump version to 2.4.2 2024-01-25 14:20:26 -08:00
shamoon
7863780883 Merge branch 'dev' 2024-01-25 14:20:11 -08:00
github-actions[bot]
c2ac9a26a2 New Crowdin translations by GitHub Action (#5547)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-25 14:19:46 -08:00
github-actions[bot]
58e8f796d1 New Crowdin translations by GitHub Action (#5539)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-25 14:18:31 -08:00
shamoon
ba0f4718e5 Fix: Modify one of date regexes (#5540) 2024-01-25 21:29:22 +00:00
shamoon
6fb4daf03e Fix: tweak doc detail component behavior while awaiting metadata (#5546) 2024-01-25 13:22:03 -08:00
Sebastian Steinbeißer
f6c34494a7 Documentation: change updating instructions (#5533) 2024-01-25 13:21:36 -08:00
shamoon
1e4d284b30 Reset dev version string 2024-01-24 11:51:41 -08:00
shamoon
1f3406fd77 Fix append changelog title 2024-01-24 11:51:32 -08:00
github-actions[bot]
398faf36fc Documentation: Add v2.4.1 changelog (#5535)
* Changelog v2.4.1 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-24 11:50:07 -08:00
shamoon
ce841d4196 Merge branch 'dev' 2024-01-24 11:24:02 -08:00
shamoon
c77f8acf41 Bump version to v2.4.1 2024-01-24 11:02:24 -08:00
github-actions[bot]
212674f9df New Crowdin translations by GitHub Action (#5488)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-24 11:01:37 -08:00
shamoon
283ced56d1 Revert "Enhancement: support remote user auth directly against API (DRF)" (#5534) 2024-01-24 11:00:44 -08:00
shamoon
530e57151d Update PULL_REQUEST_TEMPLATE.md 2024-01-24 10:31:10 -08:00
shamoon
1141c3f361 Fix: fix calendar popup header background color and disable browser autofill (#5514) 2024-01-23 19:48:41 -08:00
shamoon
49416d3372 Fix: install script fails on alpine linux due to the use of head (#5520) 2024-01-23 14:25:16 -08:00
shamoon
88ae60a4a0 Fix: enforce object permissions for app config (#5516) 2024-01-23 12:23:15 -08:00
shamoon
6651c80fb9 Remove unused workflow trigger / action perms from UI 2024-01-23 12:18:52 -08:00
shamoon
6d6650d5f6 Fix: disable clickable name edit if user does not have permissions 2024-01-22 22:12:07 -08:00
shamoon
6df252c99b Chore: Build fix- branches (#5501) 2024-01-22 16:35:45 -08:00
shamoon
5881f05dbc Change workflow permissions assignment to merge (#5496) 2024-01-22 16:34:16 -08:00
dependabot[bot]
00eba3b223 Chore(deps-dev): Bump the development group with 1 update (#5503)
Bumps the development group with 1 update: [ruff](https://github.com/astral-sh/ruff).


Updates `ruff` from 0.1.13 to 0.1.14
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.13...v0.1.14)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-22 13:45:25 -08:00
Uli Fahrer
f7ab8d23a7 Documentation: update celery monitoring docker usage (#5484)
* docs: update celery monitoring docker usage

* docs: add review comments

Co-Authored-By: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-22 10:11:28 -08:00
Joakim Berglund
85b596d20d Lowercase stack name in docker-compose.portainer.yml (#5491)
Portainer does not allow upper case letters in the stack name. Update documentation to adhere to this limitation.
2024-01-21 10:34:04 -08:00
github-actions[bot]
4d43f6b63d New Crowdin translations by GitHub Action (#5463)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-21 00:35:54 -08:00
shamoon
ea1eb551a7 Enhancement: add background to consumer statuses 2024-01-21 00:34:24 -08:00
shamoon
5842944d1e Fix: render images not converted to pdf, refactor doc detail rendering (#5475) 2024-01-20 08:26:24 -08:00
shamoon
5781a0d51f Fix frontend tests icon imports 2024-01-19 22:28:32 -08:00
shamoon
e5f48739a0 Fix: Dont parse numbers with exponent as integer (#5457) 2024-01-19 06:29:13 -08:00
shamoon
d378c861f6 Reset dev version string 2024-01-18 22:14:01 -08:00
github-actions[bot]
9466bfdb00 [Documentation] Add v2.4.0 changelog (#5453) 2024-01-18 22:11:53 -08:00
shamoon
f02e8e0dc3 Bump version to 2.4.0 2024-01-18 17:43:49 -08:00
github-actions[bot]
c1ed87a44f New Crowdin translations by GitHub Action (#5349)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-18 17:42:21 -08:00
shamoon
16169ca331 Chore: Close outdated support / general discussions (#5443) 2024-01-18 19:46:12 +00:00
shamoon
26900e0766 Fix: doc link removal before assigning value (#5451) 2024-01-18 06:58:41 -08:00
JigSaw
aa798604b3 Fix typo in bug report template (#5450) 2024-01-18 14:28:33 +00:00
shamoon
bb98fc5f65 Chore: better bootstrap icons (#5403) 2024-01-18 00:27:38 +00:00
shamoon
dc1918ad10 Fix: dont lose permissions ui if owner changed from null (#5433) 2024-01-17 17:44:04 +00:00
shamoon
ea632d0417 Fix missing frontend test imports 2024-01-16 23:01:50 -08:00
shamoon
648dc709fd Fix: tweak how auto-scrolling of logs works 2024-01-16 23:00:18 -08:00
shamoon
1a84f6a20e Fix: change auto-refresh click target 2024-01-16 22:58:41 -08:00
shamoon
96af953e6f Fix: save button layout with long button translation text 2024-01-16 21:44:41 -08:00
shamoon
6db9e292ba Enhancement: support remote user auth directly against API (DRF) (#5386) 2024-01-16 23:26:05 +00:00
shamoon
2e2362e2df Fix: outdated confirm dialog confirm 2024-01-16 15:18:26 -08:00
Trenton H
51dd95be3d Fix: Getting next ASN when no documents have an ASN (#5431)
* Fixes the next ASN logic to account for no ASNs yet being assigned

* Updates so the ASN will start at 1

* Do the same calculation without the branch
2024-01-16 23:08:37 +00:00
Trenton H
e16645b146 Feature: Add additional caching support to suggestions and metadata (#5414)
* Adds ETag and Last-Modified headers to suggestions, metadata and previews

* Slight update to the suggestions etag

* Small user message for why classifier didn't train again
2024-01-16 17:01:07 +00:00
dependabot[bot]
0068f091bb Chore(deps): Bump the small-changes group with 2 updates (#5413)
Bumps the small-changes group with 2 updates: [channels-redis](https://github.com/django/channels_redis) and [gotenberg-client](https://github.com/stumpylog/gotenberg-client).


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

Updates `gotenberg-client` from 0.4.1 to 0.5.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.4.1...0.5.0)

---
updated-dependencies:
- dependency-name: channels-redis
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: gotenberg-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-16 16:14:45 +00:00
dependabot[bot]
ad6efd2898 Chore(deps-dev): Bump the development group with 2 updates (#5412)
Bumps the development group with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.1.11 to 0.1.13
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.1.11...v0.1.13)

Updates `mkdocs-material` from 9.5.3 to 9.5.4
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.3...9.5.4)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-16 16:02:34 +00:00
shamoon
86e380bb1c Fix signin username floating label (#5424) 2024-01-16 07:32:07 -08:00
shamoon
58aacd4814 Feature: help tooltips (#5383) 2024-01-15 15:40:36 -08:00
shamoon
ad07791bac Enhancement / QoL: show selected tasks count (#5379) 2024-01-15 22:15:30 +00:00
shamoon
783090c2cd Fix shared by me filter with multiple users / groups in postgres (#5396) 2024-01-15 22:06:59 +00:00
Trenton H
41a3c7c89b Fix: Catch new warning when loading the classifier (#5395) 2024-01-14 13:21:17 -08:00
shamoon
16cc7415c1 Fix missed migration for app_logo 2024-01-13 16:23:44 -08:00
shamoon
98c5cf89ef Fix: doc detail component fixes (#5373) 2024-01-13 21:40:22 +00:00
shamoon
53e04e66cf Enhancement: warn when outdated doc detected (#5372)
* Update modified property for target docs w bidirectional links

* Warn on doc change detected
2024-01-13 20:28:10 +00:00
shamoon
2a6e79acc8 Feature: app branding (#5357) 2024-01-13 19:57:25 +00:00
Trenton H
2da5e46386 Refactor file consumption task to allow beginnings of a plugin system (#5367) 2024-01-13 16:11:14 +00:00
Trenton H
4dbf8d7969 Reapply #5304 fix 2024-01-12 13:19:24 -08:00
shamoon
4a52fc27d4 Reset dev version string 2024-01-11 21:13:51 -08:00
shamoon
05cd34c8af Merge branch 'main' into dev 2024-01-11 21:13:44 -08:00
dependabot[bot]
8a622181fc Chore(deps-dev): Bump jinja2 from 3.1.2 to 3.1.3 (#5352)
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3.
- [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.2...3.1.3)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-11 13:26:54 -08:00
github-actions[bot]
13f38bf3a1 [Documentation] Add v2.3.3 changelog (#5346)
* Changelog v2.3.3 - GHA

* Fix PR categorization, as usual

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-11 07:17:26 -08:00
shamoon
16acc2d6ad Merge branch 'dev' 2024-01-10 15:59:50 -08:00
github-actions[bot]
c2c9a953d3 New Crowdin translations by GitHub Action (#5314)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-10 15:58:17 -08:00
shamoon
530f4a8b28 Bump version to 2.3.3 2024-01-10 15:55:09 -08:00
shamoon
8eb1dc4f62 Merge branch 'dev' 2024-01-10 15:54:47 -08:00
shamoon
a2b87fe012 Trigger crowdin action on changes to frontend strings file 2024-01-10 15:54:02 -08:00
shamoon
3dcb973adb Enhancement: Explain behavior of unset app config boolean to user (#5345) 2024-01-10 15:34:20 -08:00
shamoon
8e8810cbaa Fix: correct OCR_MAX_IMAGE_PIXELS doc link 2024-01-10 13:22:05 -08:00
shamoon
b0aeec4c43 Fix: Coerce language app config field to None if empty 2024-01-10 13:21:51 -08:00
shamoon
1ac298f6ff Fix empty assign_title validation 2024-01-10 12:53:35 -08:00
shamoon
6d5f4e92cc Enhancement: title assignment placeholder error handling, fallback (#5282) 2024-01-10 10:18:55 -08:00
shamoon
416ad13aaf Update config page text 2024-01-09 11:37:39 -08:00
Trenton H
a7e1299194 Updates all backend, hooks and configures codespell in a slightly easier way (#5336) 2024-01-09 10:30:33 -08:00
Trenton H
a12e1fae72 Fix: Don't require the JSON user arguments field (#5320)
* Allows new user args field to be null

* Coerce empty string to None for user_args JSONField

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-08 13:14:36 -08:00
shamoon
f525ac0af6 Chore: add pre-commit hook for codespell (#5324) 2024-01-08 13:03:05 -08:00
luzpaz
58bf9c552b Documentation: Fix typos with automated tool (#5319)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-08 16:58:41 +00:00
github-actions[bot]
22d257cd1f Changelog v2.3.2 - GHA (#5310)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-07 23:32:11 -08:00
Trenton Holmes
f1bf1ddc54 Resets version string 2024-01-07 17:03:46 -08:00
Trenton Holmes
6015cc0e4a Bumps version to 2.3.2 2024-01-07 17:02:36 -08:00
Trenton Holmes
f9926d77d5 Merge remote-tracking branch 'origin/dev' 2024-01-07 16:47:52 -08:00
Colin Hebert
4f85dcecfc Deployment: Use the default Docker healthcheck from the Dockerfile (Part 2) (#5224)
* Set default healthcheck

* Rely on default healthcheck
2024-01-07 22:49:29 +00:00
github-actions[bot]
30c31a3d4c New Crowdin translations by GitHub Action (#5309)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-07 14:37:33 -08:00
shamoon
c64667d396 Fix: workflow assignment of customfield fails if field exists in v2.3.1 (#5302) 2024-01-07 22:27:57 +00:00
github-actions[bot]
9f6613fe05 New Crowdin translations by GitHub Action (#5268)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-07 14:19:57 -08:00
Trenton H
ea47af7034 Fixes the user arguments json field decoding (#5307) 2024-01-07 14:17:51 -08:00
shamoon
d46abeff01 Fix: empty match field cannot be saved (#5301) 2024-01-07 22:10:26 +00:00
Trenton H
2b39697ffb Fixes usages of UTC datetime instead of local datetime (#5304) 2024-01-07 13:57:40 -08:00
shamoon
4b00a72ff5 Fix: path fnmatch case note 2024-01-07 10:48:04 -08:00
shamoon
e590b2482e Update frontend strings 2024-01-07 08:46:32 -08:00
github-actions[bot]
eb7dd80410 Changelog v2.3.1 - GHA (#5283)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-01-07 08:44:12 -08:00
shamoon
86338465fb Fix: workflow edit form loses unsaved changes in v2.3.1 (#5299) 2024-01-07 08:16:58 -08:00
shamoon
a41dbdd12c Reset dev version string 2024-01-06 22:43:25 -08:00
shamoon
1e10a438cd Bump version to 2.3.1 2024-01-06 22:42:35 -08:00
shamoon
ab34ea724d Merge branch 'dev' 2024-01-06 22:42:16 -08:00
shamoon
fd8bfe1a80 Fix: edit workflow form not displaying trigger settings in v2.3.0 (#5276) 2024-01-06 17:27:49 +00:00
Trenton H
9043f45350 Adds more documentation for OCR_PAGES and prevents using 0 for actual OCR (#5275) 2024-01-06 09:06:41 -08:00
github-actions[bot]
5921e6d13e [Documentation] Add v2.3.0 changelog (#5263)
* Changelog v2.3.0 - GHA

* Re-categorize some PRs

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-06 09:06:23 -08:00
shamoon
ee2bfe2350 Reset dev version string 2024-01-05 22:12:29 -08:00
shamoon
0957a7ca8e Bump version to 2.3.0 2024-01-05 21:36:48 -08:00
shamoon
f4e75c7fb7 Merge branch 'dev' 2024-01-05 21:36:01 -08:00
shamoon
fae0e3b405 Update api version to v4 2024-01-05 21:35:40 -08:00
github-actions[bot]
ef335517ce New Crowdin translations by GitHub Action (#5146)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-05 21:28:32 -08:00
shamoon
e2be166e67 Update django.po 2024-01-05 21:26:30 -08:00
Trenton H
37e34d92de Replaces deprecated Django with standard library (#5262) 2024-01-05 21:20:38 -08:00
Trenton H
bd35030c59 Fix: Crash in barcode ASN reading when the file type isn't supported (#5261)
* Fixes a random crash in the barcode ASN reading so it doesn't try to access a not created temp dir

* Don't parse the barcodes twice, store the result instead
2024-01-06 05:08:24 +00:00
Trenton H
a82e3771ae Fix: Allows pre-consume scripts to modify the working path again (#5260)
* Allows pre-consume scripts to modify the working path again and generally cleans up some confusion about working copy vs original
2024-01-05 21:01:57 -08:00
shamoon
3115106dc1 Enhancement: add basic filters for listing custom fields (#5257) 2024-01-06 03:04:31 +00:00
shamoon
d623af9c41 Change: Use fnmatch for workflow path matching (#5250) 2024-01-05 19:15:14 +00:00
Thomas Falkenberg
355a434a07 Enhancement: fetch mails in bulk (#5249)
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-01-05 10:43:22 -08:00
Trenton H
8da2535a65 Fix: zip exports not respecting the --delete option (#5245) 2024-01-04 19:58:58 +00:00
ChrisRBe
5963dfe41b Fix: correctly format tip admonition (#5229)
format for material admonition requires a line break and another
level of indentation which was not correctly applied on this line.
2024-01-03 14:34:59 -08:00
shamoon
c6dcaa0472 Documentation: update screenshot / features list for workflows 2024-01-03 08:35:09 -08:00
shamoon
21063a5c22 Fix: workflow trigger / action ID color legibility 2024-01-03 08:33:19 -08:00
shamoon
ba2f51bed1 Fix: consistent workflow action name display 2024-01-03 00:45:22 -08:00
Bevan Kay
bbf64b7e93 Enhancement: add storage_path parameter to post_document API (#5217)
* Feature: add `storage_path` parameter to post_document API

* Complete coverage for validate_storage_path

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-01-03 08:31:56 +00:00
shamoon
3b6ce16f1c Feature: Workflows (#5121) 2024-01-03 08:19:19 +00:00
Colin Hebert
46e6be319f Set Docker default healthcheck in the Dockerfile (#5199) 2024-01-02 09:08:10 -08:00
shamoon
e6d6f21d33 Fix: fix filename format remove none when part of directory (#5210) 2024-01-02 15:17:18 +00:00
dependabot[bot]
77b9b79a9e Chore(deps): Bump the actions group with 5 updates (#5203)
* Chore(deps): Bump the actions group with 5 updates

Bumps the actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-python](https://github.com/actions/setup-python) | `4` | `5` |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `3` | `4` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `3` | `4` |
| [github/codeql-action](https://github.com/github/codeql-action) | `2` | `3` |
| [actions/stale](https://github.com/actions/stale) | `8` | `9` |


Updates `actions/setup-python` from 4 to 5
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

Updates `actions/upload-artifact` from 3 to 4
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

Updates `actions/download-artifact` from 3 to 4
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

Updates `github/codeql-action` from 2 to 3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

Updates `actions/stale` from 8 to 9
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

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

* See if this fixes broken frontend coverage actions/upload & download

---------

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>
2024-01-02 03:29:48 +00:00
dependabot[bot]
f0016ad70c Chore(deps): Bump the frontend-angular-dependencies group (#5204)
Bumps the frontend-angular-dependencies group in /src-ui with 10 updates:

| Package | From | To |
| --- | --- | --- |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `17.0.7` | `17.0.8` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `17.0.7` | `17.0.8` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `17.0.7` | `17.0.8` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `17.0.7` | `17.0.8` |
| [@angular/localize](https://github.com/angular/angular) | `17.0.7` | `17.0.8` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `17.0.7` | `17.0.8` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `17.0.7` | `17.0.8` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `17.0.7` | `17.0.8` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `17.0.7` | `17.0.8` |
| [@angular/cli](https://github.com/angular/angular-cli) | `17.0.7` | `17.0.8` |


Updates `@angular/common` from 17.0.7 to 17.0.8
- [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/17.0.8/packages/common)

Updates `@angular/compiler` from 17.0.7 to 17.0.8
- [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/17.0.8/packages/compiler)

Updates `@angular/core` from 17.0.7 to 17.0.8
- [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/17.0.8/packages/core)

Updates `@angular/forms` from 17.0.7 to 17.0.8
- [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/17.0.8/packages/forms)

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

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

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

Updates `@angular/router` from 17.0.7 to 17.0.8
- [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/17.0.8/packages/router)

Updates `@angular-devkit/build-angular` from 17.0.7 to 17.0.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/17.0.7...17.0.8)

Updates `@angular/cli` from 17.0.7 to 17.0.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/17.0.7...17.0.8)

---
updated-dependencies:
- dependency-name: "@angular/common"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/compiler"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/forms"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/localize"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/router"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/build-angular"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular/cli"
  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>
2024-01-01 21:47:37 +00:00
dependabot[bot]
054468ffc2 Chore(deps-dev): Bump @types/node from 20.10.4 to 20.10.6 in /src-ui (#5207)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 20.10.4 to 20.10.6.
- [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-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>
2024-01-01 21:30:31 +00:00
dependabot[bot]
607c1282e3 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#5205)
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 [eslint](https://github.com/eslint/eslint).


Updates `@typescript-eslint/eslint-plugin` from 6.14.0 to 6.17.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/v6.17.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 6.14.0 to 6.17.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/v6.17.0/packages/parser)

Updates `eslint` from 8.55.0 to 8.56.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/v8.55.0...v8.56.0)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  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>
2024-01-01 13:22:28 -08:00
Christoph Loy
3f4f4444f7 Document invalid options in rootless containers (#5122)
Specifying `PAPERLESS_OCR_LANGUAGES` in rootless containers is not possible since the extra languages are currently installed with apt which requires root.

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2024-01-01 08:00:05 -08:00
Antoine Libert
54372b5618 Fix: improve performance of listing result IDs (#5195) 2024-01-01 07:58:43 -08:00
Johannes Schneider
670a3f6c7f fix command for Docker version check (#5196) 2023-12-31 14:21:40 -08:00
shamoon
35a4d3fb54 Disable custom field remove button if user does not have permissions (#5194) 2023-12-31 10:55:25 -08:00
shamoon
fb81612ed1 Fix overlap of focus highlight on login screen (#5193) 2023-12-31 10:01:45 -08:00
shamoon
c5d622279c Fix symmetric doc links with target value None (#5187) 2023-12-31 07:56:26 -08:00
amo13
b93f655039 Create parent dirs of data/media/consume if necessary (#5176)
* Create parent dirs of data/media/consume if necessary
* long --parents instead of short -p
2023-12-30 19:25:34 +00:00
shamoon
428ffb4729 Fix: setting empty doc link with docs to be removed (#5174) 2023-12-30 07:43:29 -08:00
Trenton H
061f33fb05 Feature: Allow setting backend configuration settings via the UI (#5126)
* Saving some start on this

* At least partially working for the tesseract parser

* Problems with migration testing need to figure out

* Work around that error

* Fixes max m_pixels

* Moving the settings to main paperless application

* Starting some consumer options

* More fixes and work

* Fixes these last tests

* Fix max_length on OcrSettings.mode field

* Fix all fields on Common & Ocr settings serializers

* Umbrellla config view

* Revert "Umbrellla config view"

This reverts commit fbaf9f4be30f89afeb509099180158a3406416a5.

* Updates to use a single configuration object for all settings

* Squashed commit of the following:

commit 8a0a49dd57
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 23:02:47 2023 -0800

    Fix formatting

commit 66b2d90c50
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 22:36:35 2023 -0800

    Refactor frontend data models

commit 5723bd8dd8
Author: Adam Bogdał <adam@bogdal.pl>
Date:   Wed Dec 20 01:17:43 2023 +0100

    Fix: speed up admin panel for installs with a large number of documents (#5052)

commit 9b08ce1761
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 15:18:51 2023 -0800

    Update PULL_REQUEST_TEMPLATE.md

commit a6248bec2d
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 15:02:05 2023 -0800

    Chore: Update Angular to v17 (#4980)

commit b1f6f52486
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 13:53:56 2023 -0800

    Fix: Dont allow null custom_fields property via API (#5063)

commit 638d9970fd
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 13:43:50 2023 -0800

    Enhancement: symmetric document links (#4907)

commit 5e8de4c1da
Author: shamoon <4887959+shamoon@users.noreply.github.com>
Date:   Tue Dec 19 12:45:04 2023 -0800

    Enhancement: shared icon & shared by me filter (#4859)

commit 088bad9030
Author: Trenton H <797416+stumpylog@users.noreply.github.com>
Date:   Tue Dec 19 12:04:03 2023 -0800

    Bulk updates all the backend libraries (#5061)

* Saving some work on frontend config

* Very basic but dynamically-generated config form

* Saving work on slightly less ugly frontend config

* JSON validation for user_args field

* Fully dynamic config form

* Adds in some additional validators for a nicer error message

* Cleaning up the testing and coverage more

* Reverts unintentional change

* Adds documentation about the settings and the precedence

* Couple more commenting and style fixes

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-29 15:42:56 -08:00
shamoon
da058b915b Enhancement: improve validation of custom field values (#5166)
* Support all URI schemes

* Reworks custom field value validation to check and return a 400 error code in more cases and support more URL looking items, not just some basic schemes

* Fixes a spelling error in the message

---------

Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2023-12-29 14:45:29 -08:00
shamoon
cf869b1356 Fix: type casting of db values for shared by me filter (#5155) 2023-12-29 17:19:45 +00:00
shamoon
05e294fc81 Fix URL validation of empty string 2023-12-29 01:26:24 -08:00
github-actions[bot]
bd904d9e6b Changelog v2.2.1 - GHA (#5147)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-28 23:57:12 -08:00
shamoon
a7ac719711 Bump version to 2.2.1 2023-12-28 15:47:00 -08:00
shamoon
370f6ddb3b Merge branch 'dev' into main 2023-12-28 15:46:43 -08:00
github-actions[bot]
3861e84f89 New Crowdin translations by GitHub Action (#5133)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-28 15:14:10 -08:00
shamoon
76001105b8 Fix saving doc links with no value (#5144) 2023-12-28 19:57:46 +00:00
github-actions[bot]
d47cc9460b [Documentation] Add v2.2.0 changelog (#5127)
* Changelog v2.2.0 - GHA

* Fix some mis-categorized PRs

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-28 10:56:46 -08:00
shamoon
fbeb03c377 Fix: allow multiple consumption templates to assign the same custom field (#5142) 2023-12-28 10:32:33 -08:00
shamoon
2b13fa4712 Fix: dropdown with different bindlabel (#5134) 2023-12-28 08:30:52 -08:00
shamoon
98255f8e14 Reset version string 2023-12-27 23:13:56 -08:00
shamoon
eaeeb6447f Bump version to 2.2.0 2023-12-27 23:11:52 -08:00
shamoon
e6a9868e86 Merge branch 'dev' 2023-12-27 23:09:50 -08:00
shamoon
f03592d17d Update frontend strings 2023-12-27 23:09:17 -08:00
github-actions[bot]
e5db44bc2b New Crowdin translations by GitHub Action (#4998)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-27 23:08:42 -08:00
Trenton H
ffad42615f Fixes the case where a mail attachment has no filename to use (#5117) 2023-12-27 17:38:22 -08:00
shamoon
5576a073a5 Fix: Disable auto-login for API token requests (#5094) 2023-12-26 22:22:41 +00:00
Evgenii
151d337f6c Fix: update ASN regex to support Unicode (#5099) 2023-12-25 16:33:30 -08:00
Florian Bachmann
74e89b0ee3 Fix: ensure CSRF-Token on Index view (#5082) 2023-12-23 07:56:56 -08:00
shamoon
945fb675e9 Fix: Stop auto-refresh logs / tasks after close (#5089) 2023-12-23 07:47:18 -08:00
Diego Gsponer
7570945cf0 Documentation: organize API endpoints (#5077) 2023-12-22 09:27:07 -08:00
shamoon
d94d80a8c8 Enhancement: Add tooltip for select dropdown items (#5070) 2023-12-21 08:44:34 -08:00
shamoon
8a0a49dd57 Fix formatting 2023-12-19 23:02:47 -08:00
shamoon
66b2d90c50 Refactor frontend data models 2023-12-19 22:36:35 -08:00
Adam Bogdał
5723bd8dd8 Fix: speed up admin panel for installs with a large number of documents (#5052) 2023-12-19 16:17:43 -08:00
shamoon
9b08ce1761 Update PULL_REQUEST_TEMPLATE.md 2023-12-19 15:18:51 -08:00
shamoon
a6248bec2d Chore: Update Angular to v17 (#4980) 2023-12-19 15:02:05 -08:00
shamoon
b1f6f52486 Fix: Dont allow null custom_fields property via API (#5063) 2023-12-19 21:53:56 +00:00
shamoon
638d9970fd Enhancement: symmetric document links (#4907) 2023-12-19 13:43:50 -08:00
shamoon
5e8de4c1da Enhancement: shared icon & shared by me filter (#4859) 2023-12-19 20:45:04 +00:00
Trenton H
088bad9030 Bulk updates all the backend libraries (#5061) 2023-12-19 12:04:03 -08:00
dependabot[bot]
cfa908243b Chore(deps): Bump the django group with 3 updates (#5046)
Bumps the django group with 3 updates: [django](https://github.com/django/django), [django-cors-headers](https://github.com/adamchainz/django-cors-headers) and [django-filter](https://github.com/carltongibson/django-filter).


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

Updates `django-cors-headers` from 4.3.0 to 4.3.1
- [Changelog](https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/django-cors-headers/compare/4.3.0...4.3.1)

Updates `django-filter` from 23.3 to 23.5
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/main/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/23.3...23.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 15:03:54 -08:00
dependabot[bot]
cabeb00632 Chore(deps): Bump the major-versions group with 1 update (#5047)
Bumps the major-versions group with 1 update: [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF).


Updates `ocrmypdf` from 15.4.3 to 15.4.4
- [Release notes](https://github.com/ocrmypdf/OCRmyPDF/releases)
- [Changelog](https://github.com/ocrmypdf/OCRmyPDF/blob/main/docs/release_notes.rst)
- [Commits](https://github.com/ocrmypdf/OCRmyPDF/compare/v15.4.3...v15.4.4)

---
updated-dependencies:
- dependency-name: ocrmypdf
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: major-versions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 14:11:58 -08:00
dependabot[bot]
2426c01978 Chore(deps): Bump the small-changes group with 6 updates (#5048)
Bumps the small-changes group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [dateparser](https://github.com/scrapinghub/dateparser) | `1.1.8` | `1.2.0` |
| [concurrent-log-handler](https://github.com/Preston-Landers/concurrent-log-handler) | `0.9.24` | `0.9.25` |
| [mysqlclient](https://github.com/PyMySQL/mysqlclient) | `2.2.0` | `2.2.1` |
| [python-gnupg](https://github.com/vsajip/python-gnupg) | `0.5.1` | `0.5.2` |
| [python-ipware](https://github.com/un33k/python-ipware) | `2.0.0` | `2.0.1` |
| [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) | `2.1.0` | `2.2.0` |


Updates `dateparser` from 1.1.8 to 1.2.0
- [Release notes](https://github.com/scrapinghub/dateparser/releases)
- [Changelog](https://github.com/scrapinghub/dateparser/blob/master/HISTORY.rst)
- [Commits](https://github.com/scrapinghub/dateparser/compare/v1.1.8...v1.2.0)

Updates `concurrent-log-handler` from 0.9.24 to 0.9.25
- [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.24...0.9.25)

Updates `mysqlclient` from 2.2.0 to 2.2.1
- [Release notes](https://github.com/PyMySQL/mysqlclient/releases)
- [Changelog](https://github.com/PyMySQL/mysqlclient/blob/main/HISTORY.rst)
- [Commits](https://github.com/PyMySQL/mysqlclient/compare/v2.2.0...v2.2.1)

Updates `python-gnupg` from 0.5.1 to 0.5.2
- [Release notes](https://github.com/vsajip/python-gnupg/releases)
- [Changelog](https://github.com/vsajip/python-gnupg/blob/master/release)
- [Commits](https://github.com/vsajip/python-gnupg/compare/0.5.1...0.5.2)

Updates `python-ipware` from 2.0.0 to 2.0.1
- [Changelog](https://github.com/un33k/python-ipware/blob/main/CHANGELOG.md)
- [Commits](https://github.com/un33k/python-ipware/compare/v2.0.0...v2.0.1)

Updates `zxing-cpp` from 2.1.0 to 2.2.0
- [Release notes](https://github.com/zxing-cpp/zxing-cpp/releases)
- [Commits](https://github.com/zxing-cpp/zxing-cpp/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: dateparser
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: concurrent-log-handler
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: mysqlclient
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: python-gnupg
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: python-ipware
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: zxing-cpp
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 13:37:46 -08:00
Trenton H
80f2bee6e8 Filters out the not really useful warning from Django about async iterators (#5041) 2023-12-18 18:24:29 +00:00
Trenton H
7ec51758eb Fixes the display of the audit log configuration looks 2023-12-18 09:45:05 -08:00
shamoon
9e93ae952a Enhancement: Improved popup preview, respect embedded viewer, error handling (#4947) 2023-12-18 16:41:51 +00:00
Trenton H
829836ddf6 Updates Ghostscript to 10.02.1 and qpdf to 11.6.4 (#5040) 2023-12-18 07:33:00 -08:00
TTT7275
1197a048bc Enhancement: Add {original_filename}, {added_time} to title placeholders (#4972)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-17 21:38:24 -08:00
Trenton H
7289c4ea56 Feature: Allow deletion of documents via the fuzzy matching command (#4957)
* Adds new flag allowing deletion of one of a document pair which is over the match ratio

* Documents the new command option
2023-12-17 18:37:38 -08:00
shamoon
55dadf0b00 Fix shortcut keys prevented in date fields (#5009) 2023-12-18 01:07:09 +00:00
shamoon
341815cc03 Enhancement: document link field fixes (#5020)
* Implement more efficient getFew for document retrieval

* Filter out parent document ID & already-selected documents

* Clip very long document titles
2023-12-18 00:39:45 +00:00
shamoon
d22b27afe7 Enhancement: above and below doc detail save buttons (#5008)
* Show doc detail nav buttons above & below fields

* Fix tests for additional button nav

* Use flexbox to fix tab order but retain visual order

* Update screenshots
2023-12-18 00:11:12 +00:00
shamoon
6ee2d023d5 Fix password change detection on profile edit (#5028) 2023-12-17 13:36:17 -08:00
shamoon
5010cc6f15 Update bug-report.yml 2023-12-17 10:26:07 -08:00
github-actions[bot]
383cced158 Changelog v2.1.3 - GHA (#4995)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2023-12-15 18:29:49 -08:00
Trenton Holmes
a3d6967192 Resets version string 2023-12-15 17:06:06 -08:00
Trenton Holmes
c5881f75c9 Bumps version to 2.1.3 2023-12-15 17:05:41 -08:00
Trenton Holmes
c4b7429e99 Merge remote-tracking branch 'origin/dev' 2023-12-15 17:05:16 -08:00
github-actions[bot]
b1eced3612 New Crowdin translations by GitHub Action (#4967)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-15 17:03:38 -08:00
Adam Bogdał
9d5b07537d Reduce number of db queries (#4990) 2023-12-15 11:36:25 -08:00
Trenton H
122e4141b0 Fix: Document metadata is lost during barcode splitting (#4982)
* Fixes barcode splitting dropping metadata that might be needed for the round 2
2023-12-15 09:17:25 -08:00
Trenton H
be2de4f15d Fixes export of custom field instances during a split manifest export (#4984) 2023-12-14 19:23:39 -08:00
Trenton H
92a920021d Apply user arguments even in the case of the safe fallback to forcing OCR (#4981) 2023-12-14 11:20:47 -08:00
shamoon
72000cac36 Fix: show errors for select dropdowns (#4979) 2023-12-14 10:05:36 -08:00
Adam Bogdał
4510902677 Fix: Don't attempt to parse none objects during date searching 2023-12-14 07:39:49 -08:00
github-actions[bot]
c2b9d2fa7b [Documentation] Add v2.1.2 changelog (#4960)
* Changelog v2.1.2 - GHA

* Fix mis-categorized PR

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-12 18:15:37 -08:00
Trenton Holmes
cd38c39908 Resets to -dev version string 2023-12-12 17:42:20 -08:00
Trenton Holmes
9016a1e6df Bumps version to 2.1.2 2023-12-12 17:41:26 -08:00
github-actions[bot]
627254d5a7 New Crowdin translations by GitHub Action (#4892)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-12 17:29:33 -08:00
shamoon
ff31558252 Fix: Sort consumption templates by order by default (#4956) 2023-12-12 16:27:26 +00:00
Trenton H
9454978264 Updates gotenberg-client, including workaround for Gotenberg handling of non-latin filenames (#4944) 2023-12-12 15:05:33 +00:00
shamoon
e2d25a7a09 Chore: reorganize api tests (#4935)
* Move permissions-related API tests

* Move bulk-edit-related API tests

* Move bulk-download-related API tests

* Move uisettings-related API tests

* Move remoteversion-related API tests

* Move tasks API tests

* Move object-related API tests

* Move consumption-template-related API tests

* Rename pared-down documents API test file

Co-Authored-By: Trenton H <797416+stumpylog@users.noreply.github.com>
2023-12-12 04:08:51 +00:00
dependabot[bot]
85f824f032 Chore(deps-dev): Bump the small-changes group with 2 updates (#4942)
Bumps the small-changes group with 2 updates: [pre-commit](https://github.com/pre-commit/pre-commit) and [mkdocs-glightbox](https://github.com/Blueswen/mkdocs-glightbox).


Updates `pre-commit` from 3.5.0 to 3.6.0
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.5.0...v3.6.0)

Updates `mkdocs-glightbox` from 0.3.4 to 0.3.5
- [Release notes](https://github.com/Blueswen/mkdocs-glightbox/releases)
- [Changelog](https://github.com/blueswen/mkdocs-glightbox/blob/main/CHANGELOG)
- [Commits](https://github.com/Blueswen/mkdocs-glightbox/compare/v0.3.4...v0.3.5)

---
updated-dependencies:
- dependency-name: pre-commit
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: mkdocs-glightbox
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: small-changes
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 19:40:33 -08:00
shamoon
1a48910e6b Fix: allow text copy in pngx pdf viewer (#4938) 2023-12-12 01:06:30 +00:00
dependabot[bot]
bffd5829d0 Chore(deps-dev): Bump the development group with 1 update (#4939)
Bumps the development group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).

- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.4.14...9.5.2)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 15:24:01 -08:00
Trenton H
7e12bd1bef Fix: Removes the FieldParser plugin from autocomplete searches (#4934) 2023-12-11 10:21:58 -08:00
Adam Bogdał
af0817ab74 Fix: Convert search dates to UTC in advanced search (#4891)
* Index documents using local timezone

* Add local date parser
2023-12-11 09:32:43 -08:00
Trenton H
fbf1a051a2 Use the attachment filename so downstream template matching works against it (#4931) 2023-12-11 09:08:42 -08:00
shamoon
7ecf7f704a Fix: frontend handle autocomplete failure gracefully (#4903) 2023-12-11 15:41:40 +00:00
Tom Hoover
7b7a74d821 Fix: Correct spelling of 'initialization' in install script (#4928) 2023-12-11 15:10:42 +00:00
shamoon
e4acc33519 Add -dev to version string 2023-12-07 22:20:48 -08:00
github-actions[bot]
2fd141d914 [Documentation] Add v2.1.1 changelog (#4886)
* Changelog v2.1.1 - GHA

* Fix incorrectly categorized PR

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2023-12-07 22:15:29 -08:00
shamoon
aad814d342 v2.1.1 2023-12-07 21:42:02 -08:00
github-actions[bot]
7e21aaec17 New Crowdin translations by GitHub Action (#4845)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-12-07 21:39:51 -08:00
shamoon
6d5fdfe2e2 Fix: alignment of share links archive toggle 2023-12-07 14:03:57 -08:00
shamoon
5942cd6cd2 Fix: disable toggle for share link creation without archive version, fix auto-copy in Safari (#4885)
* Fix: disable share link archive switch if archive version doesnt exist

* Fix: Add brief timeout before copy after share link creation for Safari, only show if succeeded

* Update messages.xlf
2023-12-07 13:48:33 -08:00
shamoon
5cd17e71e2 Fix: storage paths link incorrect in dashboard widget (#4878) 2023-12-07 15:23:22 +00:00
shamoon
2f2ecaa61e Fix: respect baseURI for pdfjs worker URL (#4865) 2023-12-07 07:13:00 -08:00
Trenton Holmes
aa858a35e2 Merge remote-tracking branch 'origin/main' into dev 2023-12-06 19:23:12 -08:00
Trenton Holmes
d658150d42 Sets the Git information before pushing the documentation build 2023-12-06 19:20:08 -08:00
Trenton H
c312149b35 Simplifies how the documentation site is deployed (#4858) 2023-12-06 19:06:50 -08:00
Trenton H
18a9a3df12 Allow users to configure the DEFAULT_FROM_EMAIL but default to the EMAIL_HOST_USER if not set (#4867) 2023-12-06 22:52:44 +00:00
shamoon
0cdd3581c9 Fix: dont show move icon for file tasks badge (#4860) 2023-12-06 14:26:04 -08:00
github-actions[bot]
cae79b811f [Documentation] Add v2.1.0 changelog (#4846)
* Changelog v2.1.0 - GHA
* Remove a fix from the features list

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
2023-12-06 07:38:14 -08:00
Trenton Holmes
6d953babcb Resets version to -dev 2023-12-05 19:05:02 -08:00
626 changed files with 224079 additions and 108634 deletions

3
.codespellrc Normal file
View File

@@ -0,0 +1,3 @@
[codespell]
write-changes = True
ignore-words-list = criterias,afterall,valeu,ureue,equest,ure

View File

@@ -20,7 +20,7 @@ body:
- [The troubleshooting documentation](https://docs.paperless-ngx.com/troubleshooting/).
- [The installation instructions](https://docs.paperless-ngx.com/setup/#installation).
- [Existing issues and discussions](https://github.com/paperless-ngx/paperless-ngx/search?q=&type=issues).
- Disable any customer container initialization scripts, if using
- Disable any custom container initialization scripts, if using
If you encounter issues while installing or configuring Paperless-ngx, please post in the ["Support" section of the discussions](https://github.com/paperless-ngx/paperless-ngx/discussions/new?category=support).
- type: textarea
@@ -102,3 +102,14 @@ body:
attributes:
label: Other
description: Any other relevant details.
- type: checkboxes
id: required-checks
attributes:
label: Please confirm the following
options:
- label: I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
required: true
- label: I have already searched for relevant existing issues and discussions before opening this report.
required: true
- label: I have updated the title field above with a concise description.
required: true

View File

@@ -8,7 +8,11 @@ Note: All PRs with code changes should be targeted to the `dev` branch, pure doc
Please include a summary of the change and which issue is fixed (if any) and any relevant motivation / context. List any dependencies that are required for this change. If appropriate, please include an explanation of how your proposed change can be tested. Screenshots and / or videos can also be helpful if appropriate.
-->
Fixes # (issue)
<!--
⚠️ Important: Pull requests that implement a new feature or enhancement *should almost always target an existing feature request* with evidence of community interest and discussion. This is in order to balance the work of implementing and maintaining new features / enhancements. If that is not currently the case, please open a feature request instead of this PR to gather feedback from both users and the project maintainers.
-->
Closes #(issue or discussion)
## Type of change
@@ -17,10 +21,11 @@ What type of change does your PR introduce to Paperless-ngx?
NOTE: Please check only one box!
-->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Other (please explain):
- [ ] Bug fix: non-breaking change which fixes an issue.
- [ ] New feature / Enhancement: non-breaking change which adds functionality. _Please read the important note above._
- [ ] Breaking change: fix or feature that would cause existing functionality to not work as expected.
- [ ] Documentation only.
- [ ] Other. Please explain:
## Checklist:

View File

@@ -47,6 +47,8 @@ updates:
# Add reviewers
reviewers:
- "paperless-ngx/backend"
ignore:
- dependency-name: "uvicorn"
groups:
development:
patterns:

View File

@@ -16,7 +16,7 @@ on:
env:
# This is the version of pipenv all the steps will use
# If changing this, change Dockerfile
DEFAULT_PIP_ENV_VERSION: "2023.10.24"
DEFAULT_PIP_ENV_VERSION: "2023.12.1"
# This is the default version of Python to use in most steps which aren't specific
DEFAULT_PYTHON_VERSION: "3.10"
@@ -37,15 +37,15 @@ jobs:
uses: actions/checkout@v4
-
name: Install python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
-
name: Check files
uses: pre-commit/action@v3.0.0
uses: pre-commit/action@v3.0.1
documentation:
name: "Build Documentation"
name: "Build & Deploy Documentation"
runs-on: ubuntu-22.04
needs:
- pre-commit
@@ -56,7 +56,7 @@ jobs:
-
name: Set up Python
id: setup-python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
cache: "pipenv"
@@ -77,34 +77,22 @@ jobs:
name: Make documentation
run: |
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs build --config-file ./mkdocs.yml
-
name: Deploy documentation
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
echo "docs.paperless-ngx.com" > "${{ github.workspace }}/docs/CNAME"
git config --global user.name "${{ github.actor }}"
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
pipenv --python ${{ steps.setup-python.outputs.python-version }} run mkdocs gh-deploy --force --no-history
-
name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: documentation
path: site/
retention-days: 7
documentation-deploy:
name: "Deploy Documentation"
runs-on: ubuntu-22.04
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs:
- documentation
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Deploy docs
uses: mhausenblas/mkdocs-deploy-gh-pages@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CUSTOM_DOMAIN: docs.paperless-ngx.com
CONFIG_FILE: mkdocs.yml
EXTRA_PACKAGES: build-base
REQUIREMENTS: docs/requirements.txt
tests-backend:
name: "Backend Tests (Python ${{ matrix.python-version }})"
runs-on: ubuntu-22.04
@@ -121,12 +109,12 @@ jobs:
-
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
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
id: setup-python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
cache: "pipenv"
@@ -167,7 +155,7 @@ jobs:
-
name: Upload coverage
if: ${{ matrix.python-version == env.DEFAULT_PYTHON_VERSION }}
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: backend-coverage-report
path: src/coverage.xml
@@ -177,11 +165,11 @@ jobs:
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
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-depedendencies:
name: "Install Frontend Dependendencies"
name: "Install Frontend Dependencies"
runs-on: ubuntu-22.04
needs:
- pre-commit
@@ -194,9 +182,9 @@ jobs:
node-version: 20.x
cache: 'npm'
cache-dependency-path: 'src-ui/package-lock.json'
- name: Cache frontend depdendencies
- name: Cache frontend dependencies
id: cache-frontend-deps
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.npm
@@ -231,9 +219,9 @@ jobs:
node-version: 20.x
cache: 'npm'
cache-dependency-path: 'src-ui/package-lock.json'
- name: Cache frontend depdendencies
- name: Cache frontend dependencies
id: cache-frontend-deps
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.npm
@@ -250,7 +238,7 @@ jobs:
-
name: Upload Jest coverage
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: jest-coverage-report-${{ matrix.shard-index }}
path: |
@@ -265,9 +253,9 @@ jobs:
-
name: Upload Playwright test results
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: playwright-report
name: playwright-report-${{ matrix.shard-index }}
path: src-ui/playwright-report
retention-days: 7
@@ -281,13 +269,21 @@ jobs:
-
uses: actions/checkout@v4
-
name: Download frontend coverage
uses: actions/download-artifact@v3
name: Download frontend jest coverage
uses: actions/download-artifact@v4
with:
path: src-ui/coverage/
pattern: jest-coverage-report-*
-
name: Download frontend playwright coverage
uses: actions/download-artifact@v4
with:
path: src-ui/coverage/
pattern: playwright-report-*
merge-multiple: true
-
name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
# not required for public repos, but intermittently fails otherwise
token: ${{ secrets.CODECOV_TOKEN }}
@@ -297,13 +293,13 @@ jobs:
files: '!coverage.xml'
-
name: Download backend coverage
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: backend-coverage-report
path: src/
-
name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
# not required for public repos, but intermittently fails otherwise
token: ${{ secrets.CODECOV_TOKEN }}
@@ -314,7 +310,7 @@ jobs:
build-docker-image:
name: Build Docker image for ${{ github.ref_name }}
runs-on: ubuntu-22.04
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || startsWith(github.ref, 'refs/heads/fix-') || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/beta' || contains(github.ref, 'beta.rc') || startsWith(github.ref, 'refs/tags/v'))
concurrency:
group: ${{ github.workflow }}-build-docker-image-${{ github.ref_name }}
cancel-in-progress: true
@@ -428,7 +424,7 @@ jobs:
docker cp frontend-extract:/usr/src/paperless/src/documents/static/frontend src/documents/static/frontend/
-
name: Upload frontend artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: frontend-compiled
path: src/documents/static/frontend/
@@ -438,6 +434,7 @@ jobs:
name: "Build Release"
needs:
- build-docker-image
- documentation
runs-on: ubuntu-22.04
steps:
-
@@ -446,7 +443,7 @@ jobs:
-
name: Set up Python
id: setup-python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
cache: "pipenv"
@@ -472,13 +469,13 @@ jobs:
sudo apt-get install -qq --no-install-recommends gettext liblept5
-
name: Download frontend artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: frontend-compiled
path: src/documents/static/frontend/
-
name: Download documentation artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: documentation
path: docs/_build/html/
@@ -544,7 +541,7 @@ jobs:
tar -cJf paperless-ngx.tar.xz paperless-ngx/
-
name: Upload release artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: release
path: dist/paperless-ngx.tar.xz
@@ -563,7 +560,7 @@ jobs:
steps:
-
name: Download release artifact
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: release
path: ./
@@ -580,7 +577,7 @@ jobs:
-
name: Create Release and Changelog
id: create-release
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
with:
name: Paperless-ngx ${{ steps.get_version.outputs.version }}
tag: ${{ steps.get_version.outputs.version }}
@@ -614,7 +611,7 @@ jobs:
ref: main
-
name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
cache: "pipenv"
@@ -648,7 +645,7 @@ jobs:
script: |
const { repo, owner } = context.repo;
const result = await github.rest.pulls.create({
title: '[Documentation] Add ${{ needs.publish-release.outputs.version }} changelog',
title: 'Documentation: Add ${{ needs.publish-release.outputs.version }} changelog',
owner,
repo,
head: '${{ needs.publish-release.outputs.version }}-changelog',

View File

@@ -33,7 +33,7 @@ jobs:
-
name: Clean temporary images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.4.0
uses: stumpylog/image-cleaner-action/ephemeral@v0.6.0
with:
token: "${{ env.TOKEN }}"
owner: "${{ github.repository_owner }}"
@@ -41,7 +41,7 @@ jobs:
package_name: "${{ matrix.primary-name }}"
scheme: "branch"
repo_name: "paperless-ngx"
match_regex: "feature-"
match_regex: "(feature|fix)"
do_delete: "true"
cleanup-untagged-images:
@@ -61,7 +61,7 @@ jobs:
-
name: Clean untagged images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.4.0
uses: stumpylog/image-cleaner-action/untagged@v0.6.0
with:
token: "${{ env.TOKEN }}"
owner: "${{ github.repository_owner }}"

View File

@@ -42,7 +42,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
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.
@@ -51,4 +51,4 @@ jobs:
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -7,6 +7,7 @@ on:
push:
paths: [
'src/locale/**',
'src-ui/messages.xlf',
'src-ui/src/locale/**'
]
branches: [ dev ]

View File

@@ -22,6 +22,6 @@ jobs:
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
steps:
- name: Label PR with release-drafter
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
name: 'Stale'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
days-before-stale: 7
days-before-close: 14
@@ -28,7 +28,7 @@ jobs:
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.
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'
runs-on: ubuntu-latest
@@ -43,14 +43,17 @@ jobs:
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.
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.
close-answered-discussions:
name: 'Close Answered Discussions'
runs-on: ubuntu-latest
@@ -81,7 +84,7 @@ jobs:
console.log(`Found ${result.repository.discussions.nodes.length} open answered discussions`)
for (const discussion of result.repository.discussions.nodes) {
console.log(`Closing dicussion #${discussion.number} (${discussion.id})`)
console.log(`Closing discussion #${discussion.number} (${discussion.id})`)
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
@@ -90,7 +93,7 @@ jobs:
}`;
const commentVariables = {
discussion: discussion.id,
body: 'This discussion has been automatically closed because it was marked as answered.',
body: 'This discussion has been automatically closed because it was marked as answered. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
}
await github.graphql(addCommentMutation, commentVariables)
@@ -107,3 +110,172 @@ jobs:
await sleep(1000)
}
close-outdated-discussions:
name: 'Close Outdated Discussions'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const CUTOFF_DAYS = 180;
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - CUTOFF_DAYS);
const query = `query(
$owner:String!,
$name:String!,
$supportCategory:ID!,
$generalCategory:ID!,
) {
supportDiscussions: repository(owner:$owner, name:$name){
discussions(
categoryId:$supportCategory,
last:50,
answered:false,
states:[OPEN],
) {
nodes {
id,
number,
updatedAt
}
},
},
generalDiscussions: repository(owner:$owner, name:$name){
discussions(
categoryId:$generalCategory,
last:50,
states:[OPEN],
) {
nodes {
id,
number,
updatedAt
}
}
}
}`;
const variables = {
owner: context.repo.owner,
name: context.repo.repo,
supportCategory: "DIC_kwDOG1Zs184CBKWK",
generalCategory: "DIC_kwDOG1Zs184CBKWJ"
}
const result = await github.graphql(query, variables);
const combinedDiscussions = [
...result.supportDiscussions.discussions.nodes,
...result.generalDiscussions.discussions.nodes,
]
console.log(`Checking ${combinedDiscussions.length} open discussions`);
for (const discussion of combinedDiscussions) {
if (new Date(discussion.updatedAt) < cutoff) {
console.log(`Closing outdated discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt}`);
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
clientMutationId
}
}`;
const commentVariables = {
discussion: discussion.id,
body: 'This discussion has been automatically closed due to inactivity. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
}
await github.graphql(addCommentMutation, commentVariables);
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
clientMutationId
}
}`;
const closeVariables = {
discussion: discussion.id,
reason: "OUTDATED",
}
await github.graphql(closeDiscussionMutation, closeVariables);
await sleep(1000);
}
}
close-unsupported-feature-requests:
name: 'Close Unsupported Feature Requests'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const CUTOFF_1_DAYS = 180;
const CUTOFF_1_COUNT = 5;
const CUTOFF_2_DAYS = 365;
const CUTOFF_2_COUNT = 10;
const cutoff1Date = new Date();
cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS);
const cutoff2Date = new Date();
cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS);
const query = `query(
$owner:String!,
$name:String!,
$featureRequestsCategory:ID!,
) {
repository(owner:$owner, name:$name){
discussions(
categoryId:$featureRequestsCategory,
last:100,
states:[OPEN],
) {
nodes {
id,
number,
updatedAt,
upvoteCount,
}
},
}
}`;
const variables = {
owner: context.repo.owner,
name: context.repo.repo,
featureRequestsCategory: "DIC_kwDOG1Zs184CBNr4"
}
const result = await github.graphql(query, variables);
for (const discussion of result.repository.discussions.nodes) {
const discussionDate = new Date(discussion.updatedAt);
if ((discussionDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) ||
(discussionDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT)) {
console.log(`Closing discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt} with votes ${discussion.upvoteCount}`);
const addCommentMutation = `mutation($discussion:ID!, $body:String!) {
addDiscussionComment(input:{discussionId:$discussion, body:$body}) {
clientMutationId
}
}`;
const commentVariables = {
discussion: discussion.id,
body: 'This discussion has been automatically closed due to lack of community support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
}
await github.graphql(addCommentMutation, commentVariables);
const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) {
closeDiscussion(input:{discussionId:$discussion, reason:$reason}) {
clientMutationId
}
}`;
const closeVariables = {
discussion: discussion.id,
reason: "OUTDATED",
}
await github.graphql(closeDiscussionMutation, closeVariables);
await sleep(1000);
}
}

View File

@@ -5,12 +5,14 @@
repos:
# General hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: check-docstring-first
- id: check-json
exclude: "tsconfig.*json"
- id: check-yaml
args:
- "--unsafe"
- id: check-toml
- id: check-executables-have-shebangs
- id: end-of-file-fixer
@@ -26,6 +28,14 @@ repos:
- svg
- id: check-case-conflict
- id: detect-private-key
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
exclude: "(^src-ui/src/locale/)|(^src-ui/e2e/)|(^src/paperless_mail/tests/samples/)"
exclude_types:
- pofile
- json
- repo: https://github.com/pre-commit/mirrors-prettier
rev: 'v3.1.0'
hooks:
@@ -37,11 +47,11 @@ repos:
exclude: "(^Pipfile\\.lock$)"
# Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.1.5'
rev: 'v0.4.2'
hooks:
- id: ruff
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.11.0
rev: 24.4.2
hooks:
- id: black
# Dockerfile hooks
@@ -57,6 +67,6 @@ repos:
args:
- "--tab"
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: "v0.9.0.6"
rev: "v0.10.0.1"
hooks:
- id: shellcheck

View File

@@ -1,8 +1,3 @@
# https://beta.ruff.rs/docs/settings/
# https://beta.ruff.rs/docs/rules/
extend-select = ["I", "W", "UP", "COM", "DJ", "EXE", "ISC", "ICN", "G201", "INP", "PIE", "RSE", "SIM", "TID", "PLC", "PLE", "RUF"]
# TODO PTH
ignore = ["DJ001", "SIM105", "RUF012"]
fix = true
line-length = 88
respect-gitignore = true
@@ -11,13 +6,42 @@ target-version = "py39"
output-format = "grouped"
show-fixes = true
[per-file-ignores]
# https://docs.astral.sh/ruff/settings/
# https://docs.astral.sh/ruff/rules/
[lint]
extend-select = [
"W", # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
"I", # https://docs.astral.sh/ruff/rules/#isort-i
"UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up
"COM", # https://docs.astral.sh/ruff/rules/#flake8-commas-com
"DJ", # https://docs.astral.sh/ruff/rules/#flake8-django-dj
"EXE", # https://docs.astral.sh/ruff/rules/#flake8-executable-exe
"ISC", # https://docs.astral.sh/ruff/rules/#flake8-implicit-str-concat-isc
"ICN", # https://docs.astral.sh/ruff/rules/#flake8-import-conventions-icn
"G201", # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
"INP", # https://docs.astral.sh/ruff/rules/#flake8-no-pep420-inp
"PIE", # https://docs.astral.sh/ruff/rules/#flake8-pie-pie
"Q", # https://docs.astral.sh/ruff/rules/#flake8-quotes-q
"RSE", # https://docs.astral.sh/ruff/rules/#flake8-raise-rse
"T20", # https://docs.astral.sh/ruff/rules/#flake8-print-t20
"SIM", # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
"TID", # https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid
"TCH", # https://docs.astral.sh/ruff/rules/#flake8-type-checking-tch
"PLC", # https://docs.astral.sh/ruff/rules/#pylint-pl
"PLE", # https://docs.astral.sh/ruff/rules/#pylint-pl
"RUF", # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
"FLY", # https://docs.astral.sh/ruff/rules/#flynt-fly
]
# TODO PTH https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
ignore = ["DJ001", "SIM105", "RUF012"]
[lint.per-file-ignores]
".github/scripts/*.py" = ["E501", "INP001", "SIM117"]
"docker/wait-for-redis.py" = ["INP001"]
"docker/wait-for-redis.py" = ["INP001", "T201"]
"*/tests/*.py" = ["E501", "SIM117"]
"*/migrations/*.py" = ["E501", "SIM"]
"*/migrations/*.py" = ["E501", "SIM", "T201"]
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001"]
"src/documents/models.py" = ["SIM115"]
[isort]
[lint.isort]
force-single-line = true

View File

@@ -94,7 +94,7 @@ The following files need to be changed:
- src-ui/angular.json (under the _projects/paperless-ui/i18n/locales_ JSON key)
- src/paperless/settings.py (in the _LANGUAGES_ array)
- src-ui/src/app/services/settings.service.ts (inside the _getLanguageOptions_ method)
- src-ui/src/app/services/settings.service.ts (inside the _LANGUAGE_OPTIONS_ array)
- src-ui/src/app/app.module.ts (import locale from _angular/common/locales_ and call _registerLocaleData_)
Please add the language in the correct order, alphabetically by locale.
@@ -137,3 +137,19 @@ All team members are notified when mentioned or assigned to a relevant issue or
We are not overly strict with inviting people to the organization. If you have read the [team permissions](#permissions) and think having additional access would enhance your contributions, please reach out to an [admin](#structure) of the team.
The admins occasionally invite contributors directly if we believe having them on a team will accelerate their work.
# Automatic Repository Maintenance
The Paperless-ngx team appreciates all effort and interest from the community in filing bug reports, creating feature requests, sharing ideas and helping other
community members. That said, in an effort to keep the repository organized and managebale the project uses automatic handling of certain areas:
- Issues that cannot be reproduced will be marked 'stale' after 7 days of inactivity and closed after 14 further days of inactivity.
- Issues, pull requests and discussions that are closed will be locked after 30 days of inactivity.
- Discussions with a marked answer will be automatically closed.
- Discussions in the 'General' or 'Support' categories will be closed after 180 days of inactivity.
- Feature requests that do not meet the following thresholds will be closed: 5 "up-votes" after 180 days of inactivity or 10 "up-votes" after 365 days.
In all cases, threads can be re-opened by project maintainers and, of course, users can always create a new discussion for related concerns.
Finally, remember that all information remains searchable and 'closed' feature requests can still serve as inspiration for new features.
Thank you all for your contributions.

View File

@@ -12,7 +12,7 @@ COPY ./src-ui /src/src-ui
WORKDIR /src/src-ui
RUN set -eux \
&& npm update npm -g \
&& npm ci --omit=optional
&& npm ci
RUN set -eux \
&& ./node_modules/.bin/ng build --configuration production
@@ -29,7 +29,7 @@ COPY Pipfile* ./
RUN set -eux \
&& echo "Installing pipenv" \
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2023.10.24 \
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2023.12.1 \
&& echo "Generating requirement.txt" \
&& pipenv requirements > requirements.txt
@@ -52,8 +52,15 @@ ARG TARGETARCH
# Can be workflow provided, defaults set for manual building
ARG JBIG2ENC_VERSION=0.29
ARG QPDF_VERSION=11.6.3
ARG GS_VERSION=10.02.0
ARG QPDF_VERSION=11.9.0
ARG GS_VERSION=10.02.1
# Set Python environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
# Ignore warning from Whitenoise
PYTHONWARNINGS="ignore:::django.http.response:517" \
PNGX_CONTAINERIZED=1
#
# Begin installation and configuration
@@ -123,13 +130,13 @@ RUN set -eux \
&& echo "Installing Ghostscript ${GS_VERSION}" \
&& curl --fail --silent --show-error --location \
--output libgs10_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
&& curl --fail --silent --show-error --location \
--output ghostscript_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
&& curl --fail --silent --show-error --location \
--output libgs10-common_${GS_VERSION}.dfsg-2_all.deb \
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-2_all.deb \
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
&& dpkg --install ./libgs10-common_${GS_VERSION}.dfsg-2_all.deb \
&& dpkg --install ./libgs10_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
&& dpkg --install ./ghostscript_${GS_VERSION}.dfsg-2_${TARGETARCH}.deb \
@@ -187,7 +194,7 @@ RUN set -eux \
&& chmod 755 /usr/local/bin/paperless_cmd.sh \
&& mv flower-conditional.sh /usr/local/bin/flower-conditional.sh \
&& chmod 755 /usr/local/bin/flower-conditional.sh \
&& echo "Installing managment commands" \
&& echo "Installing management commands" \
&& chmod +x install_management_commands.sh \
&& ./install_management_commands.sh
@@ -268,3 +275,5 @@ ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
EXPOSE 8000
CMD ["/usr/local/bin/paperless_cmd.sh"]
HEALTHCHECK --interval=30s --timeout=10s --retries=5 CMD [ "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000" ]

19
Pipfile
View File

@@ -4,24 +4,25 @@ verify_ssl = true
name = "pypi"
[packages]
dateparser = "~=1.1"
dateparser = "~=1.2"
# WARNING: django does not use semver.
# Only patch versions are guaranteed to not introduce breaking changes.
django = "~=4.2.7"
django = "~=4.2.11"
django-allauth = {extras = ["socialaccount"], version = "*"}
django-auditlog = "*"
django-celery-results = "*"
django-compression-middleware = "*"
django-cors-headers = "*"
django-extensions = "*"
django-filter = "~=23.3"
django-filter = "~=24.2"
django-guardian = "*"
django-multiselectfield = "*"
djangorestframework = "~=3.14"
djangorestframework = "==3.14.0"
djangorestframework-guardian = "*"
drf-writable-nested = "*"
bleach = "*"
celery = {extras = ["redis"], version = "*"}
channels = "~=4.0"
channels = "~=4.1"
channels-redis = "*"
concurrent-log-handler = "*"
filelock = "*"
@@ -33,7 +34,7 @@ inotifyrecursive = "~=0.3"
langdetect = "*"
mysqlclient = "*"
nltk = "*"
ocrmypdf = "~=15.0"
ocrmypdf = "~=15.4"
pathvalidate = "*"
pdf2image = "*"
psycopg2 = "*"
@@ -45,12 +46,12 @@ python-magic = "*"
pyzbar = "*"
rapidfuzz = "*"
redis = {extras = ["hiredis"], version = "*"}
scikit-learn = "~=1.3"
scikit-learn = "~=1.4"
setproctitle = "*"
tika-client = "*"
tqdm = "*"
uvicorn = {extras = ["standard"], version = "*"}
watchdog = "~=3.0"
uvicorn = {extras = ["standard"], version = "==0.25.0"}
watchdog = "~=4.0"
whitenoise = "~=6.6"
whoosh="~=2.7"
zxing-cpp = {version = "*", platform_machine = "== 'x86_64'"}

3556
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,7 @@ Paperless-ngx is a document management system that transforms your physical docu
Paperless-ngx is the official successor to the original [Paperless](https://github.com/the-paperless-project/paperless) & [Paperless-ng](https://github.com/jonaswinkler/paperless-ng) projects and is designed to distribute the responsibility of advancing and supporting the project among a team of people. [Consider joining us!](#community-support)
A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
Thanks to the generous folks at [DigitalOcean](https://m.do.co/c/8d70b916d462), a demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com) using login `demo` / `demo`. _Note: demo content is reset frequently and confidential information should not be uploaded._
- [Features](#features)
- [Getting started](#getting-started)
@@ -30,9 +30,19 @@ A demo is available at [demo.paperless-ngx.com](https://demo.paperless-ngx.com)
- [Translation](#translation)
- [Feature Requests](#feature-requests)
- [Bugs](#bugs)
- [Affiliated Projects](#affiliated-projects)
- [Related Projects](#related-projects)
- [Important Note](#important-note)
<p align="right">This project is supported by:<br/>
<a href="https://m.do.co/c/8d70b916d462" style="padding-top: 4px; display: block;">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_white.svg" width="140px">
<source media="(prefers-color-scheme: light)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
</picture>
</a>
</p>
# Features
<picture>
@@ -53,7 +63,7 @@ If you'd like to jump right in, you can configure a `docker compose` environment
bash -c "$(curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
```
Alternatively, you can install the dependencies and setup apache and a database server yourself. The [documentation](https://docs.paperless-ngx.com/setup/#installation) has a step by step guide on how to do it.
More details and step-by-step guides for alternative installation methods can be found in [the documentation](https://docs.paperless-ngx.com/setup/#installation).
Migrating from Paperless-ng is easy, just drop in the new docker image! See the [documentation on migrating](https://docs.paperless-ngx.com/setup/#migrating-to-paperless-ngx) for more details.
@@ -83,9 +93,9 @@ Feature requests can be submitted via [GitHub Discussions](https://github.com/pa
For bugs please [open an issue](https://github.com/paperless-ngx/paperless-ngx/issues) or [start a discussion](https://github.com/paperless-ngx/paperless-ngx/discussions) if you have questions.
# Affiliated Projects
# Related Projects
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Affiliated-Projects) for a user-maintained list of affiliated projects and software that is compatible with Paperless-ngx.
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Related-Projects) for a user-maintained list of related projects and software that is compatible with Paperless-ngx.
# Important Note

9
SECURITY.md Normal file
View File

@@ -0,0 +1,9 @@
# Security Policy
## Reporting a Vulnerability
The Paperless-ngx team and community take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/paperless-ngx/paperless-ngx/security/advisories/new) tab.
The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.

View File

@@ -1,6 +1,6 @@
# Docker Compose file for running paperless testing with actual gotenberg
# and Tika containers for a more end to end test of the Tika related functionality
# Can be used locally or by the CI to start the nessecary containers with the
# Can be used locally or by the CI to start the necessary containers with the
# correct networking for the tests
version: "3.7"

View File

@@ -60,11 +60,6 @@ services:
- tika
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media

View File

@@ -54,11 +54,6 @@ services:
- broker
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
@@ -73,7 +68,6 @@ services:
PAPERLESS_DBPASS: paperless # only needed if non-default password
PAPERLESS_DBPORT: 3306
volumes:
data:
media:

View File

@@ -18,7 +18,7 @@
# To install and update paperless with this file, do the following:
#
# - Open portainer Stacks list and click 'Add stack'
# - Paste the contents of this file and assign a name, e.g. 'Paperless'
# - Paste the contents of this file and assign a name, e.g. 'paperless'
# - 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
@@ -54,11 +54,6 @@ services:
- broker
ports:
- "8010:8000"
healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media

View File

@@ -58,11 +58,6 @@ services:
- tika
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media

View File

@@ -52,11 +52,6 @@ services:
- broker
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
@@ -67,7 +62,6 @@ services:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
volumes:
data:
media:

View File

@@ -47,11 +47,6 @@ services:
- tika
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media

View File

@@ -38,11 +38,6 @@ services:
- broker
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
@@ -52,7 +47,6 @@ services:
environment:
PAPERLESS_REDIS: redis://broker:6379
volumes:
data:
media:

View File

@@ -86,17 +86,17 @@ initialize() {
"${CONSUME_DIR}"; do
if [[ ! -d "${dir}" ]]; then
echo "Creating directory ${dir}"
mkdir "${dir}"
mkdir --parents "${dir}"
fi
done
local -r tmp_dir="/tmp/paperless"
echo "Creating directory ${tmp_dir}"
mkdir -p "${tmp_dir}"
local -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}"
echo "Creating directory scratch directory ${tmp_dir}"
mkdir --parents "${tmp_dir}"
set +e
echo "Adjusting permissions of paperless files. This may take a while."
chown -R paperless:paperless ${tmp_dir}
chown -R paperless:paperless "${tmp_dir}"
for dir in \
"${export_dir}" \
"${DATA_DIR}" \

View File

@@ -80,7 +80,7 @@ django_checks() {
search_index() {
local -r index_version=7
local -r index_version=9
local -r index_version_file=${DATA_DIR}/.index_version
if [[ (! -f "${index_version_file}") || $(<"${index_version_file}") != "$index_version" ]]; then

View File

@@ -1,14 +1,15 @@
#!/usr/bin/env bash
SUPERVISORD_WORKING_DIR="${PAPERLESS_SUPERVISORD_WORKING_DIR:-$PWD}"
rootless_args=()
if [ "$(id -u)" == "$(id -u paperless)" ]; then
rootless_args=(
--user
paperless
--logfile
supervisord.log
"${SUPERVISORD_WORKING_DIR}/supervisord.log"
--pidfile
supervisord.pid
"${SUPERVISORD_WORKING_DIR}/supervisord.pid"
)
fi

View File

@@ -67,15 +67,15 @@ you installed paperless-ngx in the first place. The releases are
available at the [release
page](https://github.com/paperless-ngx/paperless-ngx/releases).
First of all, ensure that paperless is stopped.
First of all, make sure no active processes (like consumption) are running, then [make a backup](#backup).
After that, ensure that paperless is stopped:
```shell-session
$ cd /path/to/paperless
$ docker compose down
```
After that, [make a backup](#backup).
1. If you pull the image from the docker hub, all you need to do is:
```shell-session
@@ -349,11 +349,17 @@ When you use the provided docker compose script, put the export inside
the `export` folder in your paperless source directory. Specify
`../export` as the `source`.
Note that .zip files (as can be generated from the exporter) are not supported.
!!! note
Importing from a previous version of Paperless may work, but for best
results it is suggested to match the versions.
!!! warning
The importer should be run against a completely empty installation (database and directories) of Paperless-ngx.
### Document retagger {#retagger}
Say you've imported a few hundred documents and now want to introduce a
@@ -607,3 +613,10 @@ document_fuzzy_match [--ratio] [--processes N]
| ----------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| --ratio | No | 85.0 | a number between 0 and 100, setting how similar a document must be for it to be reported. Higher numbers mean more similarity. |
| --processes | No | 1/4 of system cores | Number of processes to use for matching. Setting 1 disables multiple processes |
| --delete | No | False | If provided, one document of a matched pair above the ratio will be deleted. |
!!! warning
If providing the `--delete` option, it is highly recommended to have a backup.
While every effort has been taken to ensure proper operation, there is always the
chance of deletion of a file you want to keep.

View File

@@ -136,6 +136,11 @@ script can access the following relevant environment variables set:
be triggered, leading to failures as two tasks work on the
same document path
!!! warning
If your script modifies `DOCUMENT_WORKING_PATH` in a non-deterministic
way, this may allow duplicate documents to be stored
A simple but common example for this would be creating a simple script
like this:
@@ -251,7 +256,8 @@ document. You will end up getting files like `0000123.pdf` in your media
directory. This isn't necessarily a bad thing, because you normally
don't have to access these files manually. However, if you wish to name
your files differently, you can do that by adjusting the
[`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) configuration option. Paperless adds the
[`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) configuration option
or using [storage paths (see below)](#storage-paths). Paperless adds the
correct file extension e.g. `.pdf`, `.jpg` automatically.
This variable allows you to configure the filename (folders are allowed)
@@ -284,6 +290,15 @@ will create a directory structure as follows:
paperless will report your files as missing and won't be able to find
them.
!!! tip
Paperless checks the filename of a document whenever it is saved. Changing (or deleting)
a [storage path](#storage-paths) will automatically be reflected in the file system. However,
when changing `PAPERLESS_FILENAME_FORMAT` you will need to manually run the
[`document renamer`](administration.md#renamer) to move any existing documents.
#### Placeholders
Paperless provides the following placeholders within filenames:
- `{asn}`: The archive serial number of the document, or "none".
@@ -316,6 +331,12 @@ Paperless provides the following placeholders within filenames:
- `{original_name}`: Document original filename, minus the extension, if any, or "none"
- `{doc_pk}`: The paperless identifier (primary key) for the document.
!!! warning
When using file name placeholders, in particular when using `{tag_list}`,
you may run into the limits of your operating system's maximum path lengths.
In that case, files will retain the previous path instead and the issue logged.
Paperless will try to conserve the information from your database as
much as possible. However, some characters that you can use in document
titles and correspondent names (such as `: \ /` and a couple more) are
@@ -326,34 +347,12 @@ paperless will automatically append `_01`, `_02`, etc to the filename.
This happens if all the placeholders in a filename evaluate to the same
value.
!!! tip
You can affect how empty placeholders are treated by changing the
following setting to `true`.
```
PAPERLESS_FILENAME_FORMAT_REMOVE_NONE=True
```
Doing this results in all empty placeholders resolving to "" instead
of "none" as stated above. Spaces before empty placeholders are
removed as well, empty directories are omitted.
!!! tip
Paperless checks the filename of a document whenever it is saved.
Therefore, you need to update the filenames of your documents and move
them after altering this setting by invoking the
[`document renamer`](administration.md#renamer).
!!! warning
Make absolutely sure you get the spelling of the placeholders right, or
else paperless will use the default naming scheme instead.
If there are any errors in the placeholders included in `PAPERLESS_FILENAME_FORMAT`,
paperless will fall back to using the default naming scheme instead.
!!! caution
As of now, you could totally tell paperless to store your files anywhere
As of now, you could potentially tell paperless to store your files anywhere
outside the media directory by setting
```
@@ -361,28 +360,25 @@ value.
```
However, keep in mind that inside docker, if files get stored outside of
the predefined volumes, they will be lost after a restart of paperless.
the predefined volumes, they will be lost after a restart.
!!! warning
##### Empty placeholders
When file naming handling, in particular when using `{tag_list}`,
you may run into the limits of your operating system's maximum
path lengths. Files will retain the previous path instead and
the issue logged.
You can affect how empty placeholders are treated by changing the
[`PAPERLESS_FILENAME_FORMAT_REMOVE_NONE`](configuration.md#PAPERLESS_FILENAME_FORMAT_REMOVE_NONE) setting.
## Storage paths
Enabling this results in all empty placeholders resolving to "" instead of "none" as stated above. Spaces
before empty placeholders are removed as well, empty directories are omitted.
One of the best things in Paperless is that you can not only access the
documents via the web interface, but also via the file system.
### Storage paths
When a single storage layout is not sufficient for your use case,
storage paths come to the rescue. Storage paths allow you to configure
more precisely where each document is stored in the file system.
When a single storage layout is not sufficient for your use case, storage paths allow for more complex
structure to set precisely where each document is stored in the file system.
- Each storage path is a [`PAPERLESS_FILENAME_FORMAT`](configuration.md#PAPERLESS_FILENAME_FORMAT) and
follows the rules described above
- Each document is assigned a storage path using the matching
algorithms described above, but can be overwritten at any time
- Each document is assigned a storage path using the matching algorithms described above, but can be
overwritten at any time
For example, you could define the following two storage paths:
@@ -429,8 +425,10 @@ to view more detailed information about the health of the celery workers
used for asynchronous tasks. This includes details on currently running,
queued and completed tasks, timing and more. Flower can also be used
with Prometheus, as it exports metrics. For details on its capabilities,
refer to the Flower documentation.
refer to the [Flower](https://flower.readthedocs.io/en/latest/index.html)
documentation.
Flower can be enabled with the setting [PAPERLESS_ENABLE_FLOWER](configuration.md#PAPERLESS_ENABLE_FLOWER).
To configure Flower further, create a `flowerconfig.py` and
place it into the `src/paperless` directory. For a Docker
installation, you can use volumes to accomplish this:
@@ -439,6 +437,8 @@ installation, you can use volumes to accomplish this:
services:
# ...
webserver:
environment:
- PAPERLESS_ENABLE_FLOWER
ports:
- 5555:5555 # (2)!
# ...
@@ -447,7 +447,7 @@ services:
```
1. Note the `:ro` tag means the file will be mounted as read only.
2. `flower` runs by default on port 5555, but this can be configured
2. By default, Flower runs on port 5555, but this can be configured.
## Custom Container Initialization
@@ -508,6 +508,18 @@ existing tables) with:
an older system may fix issues that can arise while setting up Paperless-ngx but
`utf8mb3` can cause issues with consumption (where `utf8mb4` does not).
### Missing timezones
MySQL as well as MariaDB do not have any timezone information by default (though some
docker images such as the official MariaDB image take care of this for you) which will
cause unexpected behavior with date-based queries.
To fix this, execute one of the following commands:
MySQL: `mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql -p`
MariaDB: `mariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb -u root mysql -p`
## Barcodes {#barcodes}
Paperless is able to utilize barcodes for automatically performing some tasks.
@@ -548,6 +560,14 @@ barcode is located. However, differing from the splitting, the page with the
barcode _will_ be retained. This allows application of a barcode to any page, including
one which holds data to keep in the document.
### Tag Assignment
When enabled, Paperless will parse barcodes and attempt to interpret and assign tags.
See the relevant settings [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE`](configuration.md#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE)
and [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING`](configuration.md#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING)
for more information.
## Automatic collation of double-sided documents {#collate}
!!! note
@@ -608,7 +628,7 @@ scan a completely new "odd numbered pages" one. The old staging file will get di
The collation feature can be used together with the [subdirs as tags](configuration.md#consume_config)
feature (but this is not a requirement). Just create a correctly named double-sided subdir
in the hierachy and upload your scans there. For example, both `double-sided/foo/bar` as
in the hierarchy and upload your scans there. For example, both `double-sided/foo/bar` as
well as `foo/bar/double-sided` will cause the collated document to be treated as if it
were uploaded into `foo/bar` and receive both `foo` and `bar` tags, but not `double-sided`.
@@ -619,3 +639,52 @@ single-sided split marker page, the split document(s) will have an empty page at
whatever else was on the backside of the split marker page.) You can work around that by having
a split marker page that has the split barcode on _both_ sides. This way, the extra page will
get automatically removed.
## SSO and third party authentication with Paperless-ngx
Paperless-ngx has a built-in authentication system from Django but you can easily integrate an
external authentication solution using one of the following methods:
### Remote User authentication
This is a simple option that uses remote user authentication made available by certain SSO
applications. See the relevant configuration options for more information:
[PAPERLESS_ENABLE_HTTP_REMOTE_USER](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER),
[PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME](configuration.md#PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME)
and [PAPERLESS_LOGOUT_REDIRECT_URL](configuration.md#PAPERLESS_LOGOUT_REDIRECT_URL)
### OpenID Connect and social authentication
Version 2.5.0 of Paperless-ngx added support for integrating other authentication systems via
the [django-allauth](https://github.com/pennersr/django-allauth) package. Once set up, users
can either log in or (optionally) sign up using any third party systems you integrate. See the
relevant [configuration settings](configuration.md#PAPERLESS_SOCIALACCOUNT_PROVIDERS) and
[django-allauth docs](https://docs.allauth.org/en/latest/socialaccount/configuration.html)
for more information.
To associate an existing Paperless-ngx account with a social account, first login with your
regular credentials and then choose "My Profile" from the user dropdown in the app and you
will see options to connect social account(s). If enabled, signup options will be available
on the login page.
As an example, to set up login via Github, the following environment variables would need to be
set:
```conf
PAPERLESS_APPS="allauth.socialaccount.providers.github"
PAPERLESS_SOCIALACCOUNT_PROVIDERS='{"github": {"APPS": [{"provider_id": "github","name": "Github","client_id": "<CLIENT_ID>","secret": "<CLIENT_SECRET>"}]}}'
```
Or, to use OpenID Connect ("OIDC"), via Keycloak in this example:
```conf
PAPERLESS_APPS="allauth.socialaccount.providers.openid_connect"
PAPERLESS_SOCIALACCOUNT_PROVIDERS='
{"openid_connect": {"APPS": [{"provider_id": "keycloak","name": "Keycloak","client_id": "paperless","secret": "<CLIENT_SECRET>","settings": { "server_url": "https://<KEYCLOAK_SERVER>/realms/<REALM>/.well-known/openid-configuration"}}]}}'
```
More details about configuration option for various providers can be found in the [allauth documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html#provider-specifics).
### Disabling Regular Login
Once external auth is set up, 'regular' login can be disabled with the [PAPERLESS_DISABLE_REGULAR_LOGIN](configuration.md#PAPERLESS_DISABLE_REGULAR_LOGIN) setting.

View File

@@ -8,20 +8,23 @@ most of the available filters and ordering fields.
The API provides the following main endpoints:
- `/api/documents/`: Full CRUD support, except POSTing new documents.
See below.
- `/api/correspondents/`: Full CRUD support.
- `/api/custom_fields/`: Full CRUD support.
- `/api/documents/`: Full CRUD support, except POSTing new documents.
See [below](#posting-documents-file-uploads).
- `/api/document_types/`: Full CRUD support.
- `/api/groups/`: Full CRUD support.
- `/api/logs/`: Read-Only.
- `/api/tags/`: Full CRUD support.
- `/api/tasks/`: Read-only.
- `/api/mail_accounts/`: Full CRUD support.
- `/api/mail_rules/`: Full CRUD support.
- `/api/users/`: Full CRUD support.
- `/api/groups/`: Full CRUD support.
- `/api/share_links/`: Full CRUD support.
- `/api/custom_fields/`: Full CRUD support.
- `/api/profile/`: GET, PATCH
- `/api/share_links/`: Full CRUD support.
- `/api/storage_paths/`: Full CRUD support.
- `/api/tags/`: Full CRUD support.
- `/api/tasks/`: Read-only.
- `/api/users/`: Full CRUD support.
- `/api/workflows/`: Full CRUD support.
- `/api/search/` GET, see [below](#global-search).
All of these endpoints except for the logging endpoint allow you to
fetch (and edit and delete where appropriate) individual objects by
@@ -56,6 +59,10 @@ fields:
- `custom_fields`: Array of custom fields & values, specified as
`{ field: CUSTOM_FIELD_ID, value: VALUE }`
!!! note
Note that all endpoint URLs must end with a `/`slash.
## Downloading documents
In addition to that, the document endpoint offers these additional
@@ -134,10 +141,11 @@ document. Paperless only reports PDF metadata at this point.
- `/api/documents/<id>/notes/`: Retrieve notes for a document.
- `/api/documents/<id>/share_links/`: Retrieve share links for a document.
- `/api/documents/<id>/history/`: Retrieve history of changes for a document.
## Authorization
The REST api provides three different forms of authentication.
The REST api provides four different forms of authentication.
1. Basic authentication
@@ -175,6 +183,44 @@ The REST api provides three different forms of authentication.
Tokens can also be managed in the Django admin.
4. Remote User authentication
If enabled (see
[configuration](configuration.md#PAPERLESS_ENABLE_HTTP_REMOTE_USER_API)),
you can authenticate against the API using Remote User auth.
## Global search
A global search endpoint is available at `/api/search/` and requires a search term
of > 2 characters e.g. `?query=foo`. This endpoint returns a maximum of 3 results
across nearly all objects, e.g. documents, tags, saved views, mail rules, etc.
Results are only included if the requesting user has the appropriate permissions.
Results are returned in the following format:
```json
{
total: number
documents: []
saved_views: []
correspondents: []
document_types: []
storage_paths: []
tags: []
users: []
groups: []
mail_accounts: []
mail_rules: []
custom_fields: []
workflows: []
}
```
Global search first searches objects by name (or title for documents) matching the query.
If the optional `db_only` parameter is set, only document titles will be searched. Otherwise,
if the amount of documents returned by a simple title string search is < 3, results from the
search index will also be included.
## Searching for documents
Full text searching is available on the `/api/documents/` endpoint. Two
@@ -183,7 +229,7 @@ results:
- `/api/documents/?query=your%20search%20query`: Search for a document
using a full text query. For details on the syntax, see [Basic Usage - Searching](usage.md#basic-usage_searching).
- `/api/documents/?more_like=1234`: Search for documents similar to
- `/api/documents/?more_like_id=1234`: Search for documents similar to
the document with id 1234.
Pagination works exactly the same as it does for normal requests on this
@@ -272,9 +318,12 @@ The endpoint supports the following optional form fields:
- `correspondent`: Specify the ID of a correspondent that the consumer
should use for the document.
- `document_type`: Similar to correspondent.
- `storage_path`: Similar to correspondent.
- `tags`: Similar to correspondent. Specify this multiple times to
have multiple tags added to the document.
- `archive_serial_number`: An optional archive serial number to set.
- `custom_fields`: An array of custom field ids to assign (with an empty
value) to the document.
The endpoint will immediately return HTTP 200 if the document consumption
process was started successfully, with the UUID of the consumption task
@@ -321,6 +370,77 @@ granted). You can pass the parameter `full_perms=true` to API calls to view the
full permissions of objects in a format that mirrors the `set_permissions`
parameter above.
## Bulk Editing
The API supports various bulk-editing operations which are executed asynchronously.
### Documents
For bulk operations on documents, use the endpoint `/api/documents/bulk_edit/` which accepts
a json payload of the format:
```json
{
"documents": [LIST_OF_DOCUMENT_IDS],
"method": METHOD, // see below
"parameters": args // see below
}
```
The following methods are supported:
- `set_correspondent`
- Requires `parameters`: `{ "correspondent": CORRESPONDENT_ID }`
- `set_document_type`
- Requires `parameters`: `{ "document_type": DOCUMENT_TYPE_ID }`
- `set_storage_path`
- Requires `parameters`: `{ "storage_path": STORAGE_PATH_ID }`
- `add_tag`
- Requires `parameters`: `{ "tag": TAG_ID }`
- `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] }`
- `delete`
- No `parameters` required
- `redo_ocr`
- No `parameters` required
- `set_permissions`
- Requires `parameters`:
- `"set_permissions": PERMISSIONS_OBJ` (see format [above](#permissions)) and / or
- `"owner": OWNER_ID or null`
- `"merge": true or false` (defaults to false)
- The `merge` flag determines if the supplied permissions will overwrite all existing permissions (including
removing them) or be merged with existing permissions.
- `merge`
- No additional `parameters` required.
- The ordering of the merged document is determined by the list of IDs.
- Optional `parameters`:
- `"metadata_document_id": DOC_ID` apply metadata (tags, correspondent, etc.) from this document to the merged document.
- `split`
- Requires `parameters`:
- `"pages": [..]` The list should be a list of pages and/or a ranges, separated by commas e.g. `"[1,2-3,4,5-7]"`
- The split operation only accepts a single document.
- `rotate`
- Requires `parameters`:
- `"degrees": DEGREES`. Must be an integer i.e. 90, 180, 270
### Objects
Bulk editing for objects (tags, document types etc.) currently supports set permissions or delete
operations, using the endpoint: `/api/bulk_edit_objects/`, which requires a json payload of the format:
```json
{
"objects": [LIST_OF_OBJECT_IDS],
"object_type": "tags", "correspondents", "document_types" or "storage_paths",
"operation": "set_permissions" or "delete",
"owner": OWNER_ID, // optional
"permissions": { "view": { "users": [] ... }, "change": { ... } }, // (see 'set_permissions' format above)
"merge": true / false // defaults to false, see above
}
```
## API Versioning
The REST API is versioned since Paperless-ngx 1.3.0.
@@ -377,3 +497,13 @@ Initial API version.
color to use for a specific tag, which is either black or white
depending on the brightness of `Tag.color`.
- Removed field `Tag.colour`.
#### Version 3
- Permissions endpoints have been added.
- The format of the `/api/ui_settings/` has changed.
#### Version 4
- Consumption templates were refactored to workflows and API endpoints
changed as such.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 KiB

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,11 @@
Paperless provides a wide range of customizations. Depending on how you
run paperless, these settings have to be defined in different places.
Certain configuration options may be set via the UI. This currently includes
common [OCR](#ocr) related settings and some frontend settings. If set, these will take
preference over the settings via environment variables. If not set, the environment setting
or applicable default will be utilized instead.
- If you run paperless on docker, `paperless.conf` is not used.
Rather, configure paperless by copying necessary options to
`docker-compose.env`.
@@ -29,6 +34,8 @@ matcher.
`redis://<username>:<password>@<host>:<port>`
- With the requirepass option PAPERLESS_REDIS =
`redis://:<password>@<host>:<port>`
- To include the redis database index PAPERLESS_REDIS =
`redis://<username>:<password>@<host>:<port>/<DBIndex>`
[More information on securing your Redis
Instance](https://redis.io/docs/getting-started/#securing-redis).
@@ -170,13 +177,13 @@ configure their endpoints, and enable the feature.
#### [`PAPERLESS_TIKA_ENDPOINT=<url>`](#PAPERLESS_TIKA_ENDPOINT) {#PAPERLESS_TIKA_ENDPOINT}
: Set the endpoint URL were Paperless can reach your Tika server.
: Set the endpoint URL where Paperless can reach your Tika server.
Defaults to "<http://localhost:9998>".
#### [`PAPERLESS_TIKA_GOTENBERG_ENDPOINT=<url>`](#PAPERLESS_TIKA_GOTENBERG_ENDPOINT) {#PAPERLESS_TIKA_GOTENBERG_ENDPOINT}
: Set the endpoint URL were Paperless can reach your Gotenberg server.
: Set the endpoint URL where Paperless can reach your Gotenberg server.
Defaults to "<http://localhost:3000>".
@@ -195,7 +202,7 @@ and watch out for indentation if editing the YAML file.
#### [`PAPERLESS_CONSUMPTION_DIR=<path>`](#PAPERLESS_CONSUMPTION_DIR) {#PAPERLESS_CONSUMPTION_DIR}
: This where your documents should go to be consumed. Make sure that
: This is where your documents should go to be consumed. Make sure that
it exists and that the user running the paperless service can
read/write its contents before you start Paperless.
@@ -257,7 +264,7 @@ directory. See [File name handling](advanced_usage.md#file-name-handling) for de
: Tells paperless to replace placeholders in
`PAPERLESS_FILENAME_FORMAT` that would resolve to
'none' to be omitted from the resulting filename. This also holds
true for directory names. See [File name handling](advanced_usage.md#file-name-handling) for
true for directory names. See [File name handling](advanced_usage.md#empty-placeholders) for
details.
Defaults to `false` which disables this feature.
@@ -447,19 +454,32 @@ applications.
This will allow authentication by simply adding a
`Remote-User: <username>` header to a request. Use with care! You
especially *must: ensure that any such header is not passed from
your proxy server to paperless.
especially *must* ensure that any such header is not passed from
external requests to your reverse-proxy to paperless (that would
effectively bypass all authentication).
If you're exposing paperless to the internet directly, do not use
this.
If you're exposing paperless to the internet directly (i.e.
without a reverse proxy), do not use this.
Also see the warning [in the official documentation](https://docs.djangoproject.com/en/4.1/howto/auth-remote-user/#configuration).
Defaults to "false" which disables this feature.
#### [`PAPERLESS_ENABLE_HTTP_REMOTE_USER_API=<bool>`](#PAPERLESS_ENABLE_HTTP_REMOTE_USER_API) {#PAPERLESS_ENABLE_HTTP_REMOTE_USER_API}
: Allows authentication via HTTP_REMOTE_USER directly against the API
!!! warning
See the warning above about securing your installation when using remote user header authentication. This setting is separate from
`PAPERLESS_ENABLE_HTTP_REMOTE_USER` to avoid introducing a security vulnerability to existing reverse proxy setups. As above,
ensure that your reverse proxy does not simply pass the `Remote-User` header from the internet to paperless.
Defaults to "false" which disables this feature.
#### [`PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME=<str>`](#PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME) {#PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME}
: If "PAPERLESS_ENABLE_HTTP_REMOTE_USER" is enabled, this
: If "PAPERLESS_ENABLE_HTTP_REMOTE_USER" or `PAPERLESS_ENABLE_HTTP_REMOTE_USER_API` are enabled, this
property allows to customize the name of the HTTP header from which
the authenticated username is extracted. Values are in terms of
[HttpRequest.META](https://docs.djangoproject.com/en/4.1/ref/request-response/#django.http.HttpRequest.META).
@@ -471,8 +491,9 @@ followed by the normalized actual header name.
#### [`PAPERLESS_LOGOUT_REDIRECT_URL=<str>`](#PAPERLESS_LOGOUT_REDIRECT_URL) {#PAPERLESS_LOGOUT_REDIRECT_URL}
: URL to redirect the user to after a logout. This can be used
together with PAPERLESS_ENABLE_HTTP_REMOTE_USER to
redirect the user back to the SSO application's logout page.
together with PAPERLESS_ENABLE_HTTP_REMOTE_USER and SSO to
redirect the user back to the SSO application's logout page to
complete the logout process.
Defaults to None, which disables this feature.
@@ -516,6 +537,64 @@ This is for use with self-signed certificates against local IMAP servers.
Settings this value has security implications for the security of your email.
Understand what it does and be sure you need to before setting.
#### [`PAPERLESS_SOCIALACCOUNT_PROVIDERS=<json>`](#PAPERLESS_SOCIALACCOUNT_PROVIDERS) {#PAPERLESS_SOCIALACCOUNT_PROVIDERS}
: This variable is used to setup login and signup via social account providers which are compatible with django-allauth.
See the corresponding [django-allauth documentation](https://docs.allauth.org/en/latest/socialaccount/providers/index.html)
for a list of provider configurations. You will also need to include the relevant Django 'application' inside the
[PAPERLESS_APPS](#PAPERLESS_APPS) setting to activate that specific authentication provider (e.g. `allauth.socialaccount.providers.openid_connect` for the [OIDC Connect provider](https://docs.allauth.org/en/latest/socialaccount/providers/openid_connect.html)).
Defaults to None, which does not enable any third party authentication systems.
#### [`PAPERLESS_SOCIAL_AUTO_SIGNUP=<bool>`](#PAPERLESS_SOCIAL_AUTO_SIGNUP) {#PAPERLESS_SOCIAL_AUTO_SIGNUP}
: Attempt to signup the user using retrieved email, username etc from the third party authentication
system. See the corresponding
[django-allauth documentation](https://docs.allauth.org/en/latest/socialaccount/configuration.html)
Defaults to False
#### [`PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS=<bool>`](#PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS) {#PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS}
: Allow users to signup for a new Paperless-ngx account using any setup third party authentication systems.
Defaults to True
#### [`PAPERLESS_ACCOUNT_ALLOW_SIGNUPS=<bool>`](#PAPERLESS_ACCOUNT_ALLOW_SIGNUPS) {#PAPERLESS_ACCOUNT_ALLOW_SIGNUPS}
: Allow users to signup for a new Paperless-ngx account.
Defaults to False
#### [`PAPERLESS_ACCOUNT_DEFAULT_HTTP_PROTOCOL=<string>`](#PAPERLESS_ACCOUNT_DEFAULT_HTTP_PROTOCOL) {#PAPERLESS_ACCOUNT_DEFAULT_HTTP_PROTOCOL}
: The protocol used when generating URLs, e.g. login callback URLs. See the corresponding
[django-allauth documentation](https://docs.allauth.org/en/latest/account/configuration.html)
Defaults to 'https'
#### [`PAPERLESS_ACCOUNT_EMAIL_VERIFICATION=<string>`](#PAPERLESS_ACCOUNT_EMAIL_VERIFICATION) {#PAPERLESS_ACCOUNT_EMAIL_VERIFICATION}
: Determines whether email addresses are verified during signup (as performed by Django allauth). See the relevant
[paperless settings](#PAPERLESS_EMAIL_HOST) and [the allauth docs](https://docs.allauth.org/en/latest/account/configuration.html)
Defaults to 'optional'
!!! note
If you do not have a working email server set up you should set this to 'none'.
#### [`PAPERLESS_DISABLE_REGULAR_LOGIN=<bool>`](#PAPERLESS_DISABLE_REGULAR_LOGIN) {#PAPERLESS_DISABLE_REGULAR_LOGIN}
: Disables the regular frontend username / password login, i.e. once you have setup SSO. Note that this setting does not disable the Django admin login. To prevent logins directly to Django, consider blocking `/admin/` in your [web server or reverse proxy configuration](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx).
Defaults to False
#### [`PAPERLESS_ACCOUNT_SESSION_REMEMBER=<bool>`](#PAPERLESS_ACCOUNT_SESSION_REMEMBER) {#PAPERLESS_ACCOUNT_SESSION_REMEMBER}
: See the corresponding
[django-allauth documentation](https://docs.allauth.org/en/latest/account/configuration.html)
## OCR settings {#ocr}
Paperless uses [OCRmyPDF](https://ocrmypdf.readthedocs.io/en/latest/)
@@ -660,11 +739,13 @@ completely.
Specifying 1 here will only use the first page.
The value must be greater than or equal to 1 to be used.
When combined with `PAPERLESS_OCR_MODE=redo` or
`PAPERLESS_OCR_MODE=force`, paperless will not modify any text it
finds on excluded pages and copy it verbatim.
Defaults to 0, which disables this feature and always uses all
Defaults to unset, which disables this feature and always uses all
pages.
#### [`PAPERLESS_OCR_IMAGE_DPI=<num>`](#PAPERLESS_OCR_IMAGE_DPI) {#PAPERLESS_OCR_IMAGE_DPI}
@@ -678,7 +759,7 @@ fails, it uses this value as a fallback.
Set this to the DPI your scanner produces images at.
Default is none, which will automatically calculate image DPI so
Defaults to unset, which will automatically calculate image DPI so
that the produced PDF documents are A4 sized.
#### [`PAPERLESS_OCR_MAX_IMAGE_PIXELS=<num>`](#PAPERLESS_OCR_MAX_IMAGE_PIXELS) {#PAPERLESS_OCR_MAX_IMAGE_PIXELS}
@@ -691,6 +772,8 @@ but could result in missing text content.
If unset, will default to the value determined by
[Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.MAX_IMAGE_PIXELS).
Setting this value to 0 will entirely disable the limit. See the below warning.
!!! note
Increasing this limit could cause Paperless to consume additional
@@ -700,7 +783,7 @@ but could result in missing text content.
!!! warning
The limit is intended to prevent malicious files from consuming
system resources and causing crashes and other errors. Only increase
system resources and causing crashes and other errors. Only change
this value if you are certain your documents are not malicious and
you need the text which was not OCRed
@@ -733,7 +816,7 @@ they use underscores instead of dashes.
Paperless has been tested to work with the OCR options provided
above. There are many options that are incompatible with each other,
so specifying invalid options may prevent paperless from consuming
any documents.
any documents. Use with caution!
Specify arguments as a JSON dictionary. Keep note of lower case
booleans and double quoted parameter names and strings. Examples:
@@ -884,6 +967,28 @@ documents.
Default is none, which disables the temporary directory.
#### [`PAPERLESS_APPS=<string>`](#PAPERLESS_APPS) {#PAPERLESS_APPS}
: A comma-separated list of Django apps to be included in Django's
[`INSTALLED_APPS`](https://docs.djangoproject.com/en/5.0/ref/applications/). This setting should
be used with caution!
Defaults to None, which does not add any additional apps.
#### [`PAPERLESS_MAX_IMAGE_PIXELS=<number>`](#PAPERLESS_MAX_IMAGE_PIXELS) {#PAPERLESS_MAX_IMAGE_PIXELS}
: Configures the maximum size of an image PIL will allow to load without warning or error.
: If unset, will default to the value determined by
[Pillow](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.MAX_IMAGE_PIXELS).
Defaults to None, which does change the limit
!!! warning
This limit is designed to prevent denial of service from malicious files.
It should only be raised or disabled in certain circumstances and with great care.
## Document Consumption {#consume_config}
#### [`PAPERLESS_CONSUMER_DELETE_DUPLICATES=<bool>`](#PAPERLESS_CONSUMER_DELETE_DUPLICATES) {#PAPERLESS_CONSUMER_DELETE_DUPLICATES}
@@ -931,7 +1036,7 @@ or hidden folders some tools use to store data.
`._foo.pdf` and `._bar/foo.pdf`
Defaults to
`[".DS_STORE/*", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini", "@eaDir/*"]`.
`[".DS_Store", ".DS_STORE", "._*", ".stfolder/*", ".stversions/*", ".localized/*", "desktop.ini", "@eaDir/*", "Thumbs.db"]`.
#### [`PAPERLESS_CONSUMER_BARCODE_SCANNER=<string>`](#PAPERLESS_CONSUMER_BARCODE_SCANNER) {#PAPERLESS_CONSUMER_BARCODE_SCANNER}
@@ -981,7 +1086,7 @@ document text will be checked as normal.
: Paperless searches an entire document for dates. The first date
found will be used as the initial value for the created date. When
this variable is greater than 0 (or left to it's default value),
this variable is greater than 0 (or left to its default value),
paperless will also suggest other dates found in the document, up to
a maximum of this setting. Note that duplicates will be removed,
which can result in fewer dates displayed in the frontend than this
@@ -1006,11 +1111,11 @@ This font can be changed here.
#### [`PAPERLESS_IGNORE_DATES=<string>`](#PAPERLESS_IGNORE_DATES) {#PAPERLESS_IGNORE_DATES}
: Paperless parses a documents creation date from filename and file
: Paperless parses a document's creation date from filename and file
content. You may specify a comma separated list of dates that should
be ignored during this process. This is useful for special dates
(like date of birth) that appear in documents regularly but are very
unlikely to be the documents creation date.
unlikely to be the document's creation date.
The date is parsed using the order specified in PAPERLESS_DATE_ORDER
@@ -1042,8 +1147,10 @@ system changes with `inotify`.
#### [`PAPERLESS_CONSUMER_POLLING_RETRY_COUNT=<num>`](#PAPERLESS_CONSUMER_POLLING_RETRY_COUNT) {#PAPERLESS_CONSUMER_POLLING_RETRY_COUNT}
: If consumer polling is enabled, sets the number of times paperless
will check for a file to remain unmodified.
: If consumer polling is enabled, sets the maximum number of times
paperless will check for a file to remain unmodified. If a file's
modification time and size are identical for two consecutive checks, it
will be consumed.
Defaults to 5.
@@ -1135,7 +1242,7 @@ barcode.
: Defines the upscale factor used in barcode detection.
Improves the detection of small barcodes, i.e. with a value of 1.5 by
upscaling the document beforce the detection process. Upscaling will
upscaling the document before the detection process. Upscaling will
only take place if value is bigger than 1.0. Otherwise upscaling will
not be performed to save resources. Try using in combination with
PAPERLESS_CONSUMER_BARCODE_DPI set to a value higher than default.
@@ -1152,14 +1259,62 @@ combination with PAPERLESS_CONSUMER_BARCODE_UPSCALE bigger than 1.0.
Defaults to "300"
#### [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=<bool>`](#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE) {#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE}
: Enables the detection of barcodes in the scanned document and
assigns or creates tags if a properly formatted barcode is detected.
The barcode must match one of the (configurable) regular expressions.
If the barcode text contains ',' (comma), it is split into multiple
barcodes which are individually processed for tagging.
Matching is case insensitive.
Defaults to false.
#### [`PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING=<json dict>`](#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING) {#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING}
: Defines a dictionary of filter regex and substitute expressions.
Syntax: `{"<regex>": "<substitute>" [,...]]}`
A barcode is considered for tagging if the barcode text matches
at least one of the provided <regex> pattern.
If a match is found, the <substitute> rule is applied. This allows very
versatile reformatting and mapping of barcode pattern to tag values.
If a tag is not found it will be created.
Defaults to:
`{"TAG:(.*)": "\\g<1>"}` which defines
- a regex TAG:(.*) which includes barcodes beginning with TAG:
followed by any text that gets stored into match group #1 and
- a substitute `\\g<1>` that replaces the original barcode text
by the content in match group #1.
Consequently, the tag is the barcode text without its TAG: prefix.
More examples:
`{"ASN12.*": "JOHN", "ASN13.*": "SMITH"}` for example maps
- ASN12nnnn barcodes to the tag JOHN and
- ASN13nnnn barcodes to the tag SMITH.
`{"T-J": "JOHN", "T-S": "SMITH", "T-D": "DOE"}` directly maps
- T-J barcodes to the tag JOHN,
- T-S barcodes to the tag SMITH and
- T-D barcodes to the tag DOE.
Please refer to the Python regex documentation for more information.
## Audit Trail
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED){#PAPERLESS_AUDIT_LOG_ENABLED}
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED}
: Enables an audit trail for documents, document types, correspondents, and tags. Log entries can be viewed in the Django backend only.
: Enables the audit trail for documents, document types, correspondents, and tags.
!!! warning
Once enabled cannot be disabled
Defaults to true.
## Collate Double-Sided Documents {#collate}
@@ -1298,7 +1453,7 @@ specified as "chi-tra".
PAPERLESS_OCR_LANGUAGES=tur ces chi-tra
```
Make sure it's a space separated list when using several values.
Make sure it's a space-separated list when using several values.
To actually use these languages, also set the default OCR language
of paperless:
@@ -1309,6 +1464,10 @@ specified as "chi-tra".
Defaults to none, which does not install any additional languages.
!!! warning
This option must not be used in rootless containers.
#### [`PAPERLESS_ENABLE_FLOWER=<defined>`](#PAPERLESS_ENABLE_FLOWER) {#PAPERLESS_ENABLE_FLOWER}
: If this environment variable is defined, the Celery monitoring tool
@@ -1317,7 +1476,21 @@ started by the container.
You can read more about this in the [advanced documentation](advanced_usage.md#celery-monitoring).
## Update Checking {#update-checking}
#### [`PAPERLESS_SUPERVISORD_WORKING_DIR=<defined>`](#PAPERLESS_SUPERVISORD_WORKING_DIR) {#PAPERLESS_SUPERVISORD_WORKING_DIR}
: If this environment variable is defined, the `supervisord.log` and `supervisord.pid` file will be created under the specified path in `PAPERLESS_SUPERVISORD_WORKING_DIR`. Setting `PAPERLESS_SUPERVISORD_WORKING_DIR=/tmp` and `PYTHONPYCACHEPREFIX=/tmp/pycache` would allow paperless to work on a read-only filesystem.
Please take note that the `PAPERLESS_DATA_DIR` and `PAPERLESS_MEDIA_ROOT` paths still have to be writable, just like the `PAPERLESS_SUPERVISORD_WORKING_DIR`. The can be archived by using bind or volume mounts. Only works in the container is run as user *paperless*
## Frontend Settings
#### [`PAPERLESS_APP_TITLE=<str>`](#PAPERLESS_APP_TITLE) {#PAPERLESS_APP_TITLE}
: If set, overrides the default name "Paperless-ngx"
#### [`PAPERLESS_APP_LOGO=<path>`](#PAPERLESS_APP_LOGO) {#PAPERLESS_APP_LOGO}
: Path to an image file in the /media/logo directory, must include 'logo', e.g. `/logo/Atari_logo.svg`
#### [`PAPERLESS_ENABLE_UPDATE_CHECK=<bool>`](#PAPERLESS_ENABLE_UPDATE_CHECK) {#PAPERLESS_ENABLE_UPDATE_CHECK}
@@ -1345,6 +1518,10 @@ password. All of these options come from their similarly-named [Django settings]
: Defaults to ''.
#### [`PAPERLESS_EMAIL_FROM=<str>`](#PAPERLESS_EMAIL_FROM) {#PAPERLESS_EMAIL_FROM}
: Defaults to PAPERLESS_EMAIL_HOST_USER if not set.
#### [`PAPERLESS_EMAIL_HOST_PASSWORD=<str>`](#PAPERLESS_EMAIL_HOST_PASSWORD) {#PAPERLESS_EMAIL_HOST_PASSWORD}
: Defaults to ''.

View File

@@ -277,27 +277,17 @@ Adding new languages requires adding the translated files in the
}
```
2. Add the language to the available options in
2. Add the language to the `LANGUAGE_OPTIONS` array in
`src-ui/src/app/services/settings.service.ts`:
```typescript
getLanguageOptions(): LanguageOption[] {
return [
{code: "en-us", name: $localize`English (US)`, englishName: "English (US)", dateInputFormat: "mm/dd/yyyy"},
{code: "en-gb", name: $localize`English (GB)`, englishName: "English (GB)", dateInputFormat: "dd/mm/yyyy"},
{code: "de", name: $localize`German`, englishName: "German", dateInputFormat: "dd.mm.yyyy"},
{code: "nl", name: $localize`Dutch`, englishName: "Dutch", dateInputFormat: "dd-mm-yyyy"},
{code: "fr", name: $localize`French`, englishName: "French", dateInputFormat: "dd/mm/yyyy"},
{code: "pt-br", name: $localize`Portuguese (Brazil)`, englishName: "Portuguese (Brazil)", dateInputFormat: "dd/mm/yyyy"}
// Add your new language here
]
}
```
`dateInputFormat` is a special string that defines the behavior of
the date input fields and absolutely needs to contain "dd", "mm"
and "yyyy".
```
3. Import and register the Angular data for this locale in
`src-ui/src/app/app.module.ts`:

View File

@@ -8,6 +8,13 @@ physical documents into a searchable online archive so you can keep, well, _less
[Get started](setup.md){ .md-button .md-button--primary .index-callout }
[Demo](https://demo.paperless-ngx.com){ .md-button .md-button--secondary target=\_blank }
<div style="display: flex; justify-content: end; margin-top: -1.5rem;">
<a href="https://m.do.co/c/8d70b916d462" target="_blank">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_white.svg#only-dark" class="no-lightbox" width="150px">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_black.svg#only-light" class="no-lightbox" width="150px">
</a>
</div>
</div>
<div class="grid-right" markdown>
![image](assets/screenshots/documents-smallcards.png#only-light){.index-screenshot}
@@ -18,6 +25,7 @@ physical documents into a searchable online archive so you can keep, well, _less
## Features
- **Organize and index** your scanned documents with tags, correspondents, types, and more.
- _Your_ data is stored locally on _your_ server and is never transmitted or shared in any way.
- Performs **OCR** on your documents, adding searchable and selectable text, even to documents scanned with only images.
- Utilizes the open-source Tesseract engine to recognize more than 100 languages.
- Documents are saved as PDF/A format which is designed for long term storage, alongside the unaltered originals.
@@ -41,7 +49,7 @@ physical documents into a searchable online archive so you can keep, well, _less
- Configure multiple accounts and rules for each account.
- After processing, paperless can perform actions on the messages such as marking as read, deleting and more.
- A built-in robust **multi-user permissions** system that supports 'global' permissions as well as per document or object.
- A powerful templating system that gives you more control over the consumption pipeline.
- A powerful workflow system that gives you even more control.
- **Optimized** for multi core systems: Paperless-ngx consumes multiple documents in parallel.
- The integrated sanity checker makes sure that your document archive is in good health.
@@ -156,9 +164,9 @@ Tag, correspondent, document type and storage path editing.
</div>
<div class="grid-half-right" markdown>
Consumption templates provide finer control over the document pipeline.
Workflows provide finer control over the document pipeline and trigger actions.
![image](assets/screenshots/consumption_template.png)
![image](assets/screenshots/workflow.png)
</div>
<div class="clear"></div>

View File

@@ -1,2 +0,0 @@
-i https://pypi.python.org/simple
mkdocs-glightbox==0.3.4; python_version >= '3.8'

View File

@@ -6,6 +6,7 @@ You can go multiple routes to setup and run Paperless:
- [Pull the image from Docker Hub](#docker_hub)
- [Build the Docker image yourself](#docker_build)
- [Install Paperless directly on your system manually (bare metal)](#bare_metal)
- A user-maintained list of commercial hosting providers can be found [in the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Related-Projects)
The Docker routes are quick & easy. These are the recommended routes.
This configures all the stuff from the above automatically so that it
@@ -28,7 +29,8 @@ steps described in [Docker setup](#docker_hub) automatically.
1. Make sure that Docker and Docker Compose are installed.
!!! tip
See the Docker installation instructions at https://docs.docker.com/engine/install/
See the Docker installation instructions at https://docs.docker.com/engine/install/
2. Download and run the installation script:
@@ -72,7 +74,7 @@ steps described in [Docker setup](#docker_hub) automatically.
If you want to use the included `docker-compose.*.yml` file, you
need to have at least Docker version **17.09.0** and Docker Compose
version **v2**. To check do: `docker compose -v` or `docker -v`
version **v2**. To check do: `docker compose version` or `docker -v`
See the [Docker installation guide](https://docs.docker.com/engine/install/) on how to install the current
version of Docker for your operating system or Linux distribution of
@@ -95,7 +97,7 @@ steps described in [Docker setup](#docker_hub) automatically.
- /home/jonaswinkler/paperless-inbox:/usr/src/paperless/consume
```
Don't change the part after the colon or paperless wont find your
Don't change the part after the colon or paperless won't find your
documents.
You may also need to change the default port that the webserver will
@@ -120,6 +122,10 @@ steps described in [Docker setup](#docker_hub) automatically.
**Rootless**
!!! warning
It is currently not possible to run the container rootless if additional languages are specified via `PAPERLESS_OCR_LANGUAGES`.
If you want to run Paperless as a rootless container, you will need
to do the following in your `docker-compose.yml`:
@@ -686,95 +692,8 @@ commands as well.
## Moving data from SQLite to PostgreSQL or MySQL/MariaDB {#sqlite_to_psql}
Moving your data from SQLite to PostgreSQL or MySQL/MariaDB is done via
executing a series of django management commands as below. The commands
below use PostgreSQL, but are applicable to MySQL/MariaDB with the
!!! warning
Make sure that your SQLite database is migrated to the latest version.
Starting paperless will make sure that this is the case. If your try to
load data from an old database schema in SQLite into a newer database
schema in PostgreSQL, you will run into trouble.
!!! warning
On some database fields, PostgreSQL enforces predefined limits on
maximum length, whereas SQLite does not. The fields in question are the
title of documents (128 characters), names of document types, tags and
correspondents (128 characters), and filenames (1024 characters). If you
have data in these fields that surpasses these limits, migration to
PostgreSQL is not possible and will fail with an error.
!!! warning
MySQL is case insensitive by default, treating values like "Name" and
"NAME" as identical. See [MySQL caveats](advanced_usage.md#mysql-caveats) for details.
!!! warning
MySQL also enforces limits on maximum lengths, but does so differently than
PostgreSQL. It may not be possible to migrate to MySQL due to this.
!!! warning
Using mariadb version 10.4+ is recommended. Using the `utf8mb3` character set on
an older system may fix issues that can arise while setting up Paperless-ngx but
`utf8mb3` can cause issues with consumption (where `utf8mb4` does not).
1. Stop paperless, if it is running.
2. Tell paperless to use PostgreSQL:
a) With docker, copy the provided `docker-compose.postgres.yml`
file to `docker-compose.yml`. Remember to adjust the consumption
directory, if necessary.
b) Without docker, configure the database in your `paperless.conf`
file. See [configuration](configuration.md) for
details.
3. Open a shell and initialize the database:
a) With docker, run the following command to open a shell within
the paperless container:
``` shell-session
$ cd /path/to/paperless
$ docker compose run --rm webserver /bin/bash
```
This will launch the container and initialize the PostgreSQL
database.
b) Without docker, remember to activate any virtual environment,
switch to the `src` directory and create the database schema:
``` shell-session
$ cd /path/to/paperless/src
$ python3 manage.py migrate
```
This will not copy any data yet.
4. Dump your data from SQLite:
```shell-session
$ python3 manage.py dumpdata --database=sqlite --exclude=contenttypes --exclude=auth.Permission > data.json
```
5. Load your data into PostgreSQL:
```shell-session
$ python3 manage.py loaddata data.json
```
6. If operating inside Docker, you may exit the shell now.
```shell-session
$ exit
```
7. Start paperless.
The best way to migrate between database types is to perform an [export](administration.md#exporter) and then
[import](administration.md#importer) into a clean installation of Paperless-ngx.
## Moving back to Paperless

View File

@@ -138,7 +138,7 @@ command:
You might encounter errors such as:
```shell-session
The following error occured while consuming document.pdf: [Errno 13] Permission denied: '/usr/src/paperless/src/../consume/document.pdf'
The following error occurred while consuming document.pdf: [Errno 13] Permission denied: '/usr/src/paperless/src/../consume/document.pdf'
```
This happens when paperless does not have permission to delete files

View File

@@ -109,7 +109,7 @@ process.
### Mobile upload {#usage-mobile_upload}
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Affiliated-Projects) for a user-maintained list of affiliated projects and
Please see [the wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Related-Projects) for a user-maintained list of related projects and
software (e.g. for mobile devices) that is compatible with Paperless-ngx.
### IMAP (Email) {#usage-email}
@@ -149,7 +149,7 @@ different means. These are as follows:
- **Flag:** Sets the 'important' flag on mails with consumed
documents. Paperless will not consume flagged mails.
- **Move to folder:** Moves consumed mails out of the way so that
paperless wont consume them again.
paperless won't consume them again.
- **Add custom Tag:** Adds a custom tag to mails with consumed
documents (the IMAP standard calls these "keywords"). Paperless
will not consume mails already tagged. Not all mail servers support
@@ -206,12 +206,12 @@ for details.
## Permissions
As of version 1.14.0 Paperless-ngx added core support for user / group permissions. Permissions is
based around 'global' permissions as well as 'object-level' permissions. Global permissions designate
which parts of the application a user can access (e.g. Documents, Tags, Settings) and object-level
determine which objects are visible or editable. All objects have an 'owner' and 'view' and 'edit'
permissions which can be granted to other users or groups. The paperless-ngx permissions system uses
the built-in user model of the backend framework, Django.
Permissions in Paperless-ngx are based around ['global' permissions](#global-permissions) as well as
['object-level' permissions](#object-permissions). Global permissions determine which parts of the
application a user can access (e.g. Documents, Tags, Settings) and object-level determine which
objects are visible or editable. All objects have an 'owner' and 'view' and 'edit' permissions which
can be granted to other users or groups. The paperless-ngx permissions system uses the built-in user
model of the backend framework, Django.
!!! tip
@@ -219,87 +219,166 @@ the built-in user model of the backend framework, Django.
for a Tag will _not_ affect the permissions of documents that have the Tag.
Permissions can be set using the new "Permissions" tab when editing documents, or bulk-applied
in the UI by selecting documents and choosing the "Permissions" button. Owner can also optionally
be set for documents uploaded via the API. Documents consumed via the consumption dir currently
do not have an owner set.
!!! note
After migration to version 1.14.0 all existing documents, tags etc. will have no explicit owner
set which means they will be visible / editable by all users. Once an object has an owner set,
only the owner can explicitly grant / revoke permissions.
!!! note
When first migrating to permissions it is recommended to use a 'superuser' account (which
would usually have been setup during installation) to ensure you have full permissions.
Note that superusers have access to all objects.
in the UI by selecting documents and choosing the "Permissions" button.
### Default permissions
Default permissions for documents can be set using consumption templates.
[Workflows](#workflows) provide advanced ways to control permissions.
For objects created via the web UI (tags, doc types, etc.) the default is to set the current user
as owner and no extra permissions, but you explicitly set these under Settings > Permissions.
as owner and no extra permissions, but you can explicitly set these under Settings > Permissions.
Documents consumed via the consumption directory do not have an owner or additional permissions set by default, but again, can be controlled with [Workflows](#workflows).
### Users and Groups
Paperless-ngx versions after 1.14.0 allow creating and editing users and groups via the 'frontend' UI.
These can be found under Settings > Users & Groups, assuming the user has access. If a user is designated
Paperless-ngx supports editing users and groups via the 'frontend' UI, which can be found under
Settings > Users & Groups, assuming the user has access. If a user is designated
as a member of a group those permissions will be inherited and this is reflected in the UI. Explicit
permissions can be granted to limit access to certain parts of the UI (and corresponding API endpoints).
!!! note
Superusers can access all parts of the front and backend application as well as any and all objects.
#### Admin Status
Admin status (Django 'staff status') grants access to viewing the paperless logs and the system status dialog
as well as accessing the Django backend.
#### Detailed Explanation of Global Permissions {#global-permissions}
Global permissions define what areas of the app and API endpoints the user can access. For example, they
determine if a user can create, edit, delete or view _any_ documents, but individual documents themselves
still have "object-level" permissions.
| Type | Details |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AppConfig | _Change_ or higher permissions grants access to the "Application Configuration" area. |
| Correspondent | Grants global permissions to add, edit, delete or view Correspondents. |
| CustomField | Grants global permissions to add, edit, delete or view Custom Fields. |
| Document | Grants global permissions to add, edit, delete or view Documents. |
| DocumentType | Grants global permissions to add, edit, delete or view Document Types. |
| Group | Grants global permissions to add, edit, delete or view Groups. |
| MailAccount | Grants global permissions to add, edit, delete or view Mail Accounts. |
| MailRule | Grants global permissions to add, edit, delete or view Mail Rules. |
| Note | Grants global permissions to add, edit, delete or view Notes. |
| PaperlessTask | Grants global permissions to view or dismiss (_Change_) File Tasks. |
| SavedView | Grants global permissions to add, edit, delete or view Saved Views. |
| ShareLink | Grants global permissions to add, delete or view Share Links. |
| StoragePath | Grants global permissions to add, edit, delete or view Storage Paths. |
| Tag | Grants global permissions to add, edit, delete or view Tags. |
| UISettings | Grants global permissions to add, edit, delete or view the UI settings that are used by the web app.<br/>Users expected to access the web UI should usually be granted at least _View_ permissions. |
| User | Grants global permissions to add, edit, delete or view Users. |
| Workflow | Grants global permissions to add, edit, delete or view Workflows.<br/>Note that Workflows are global, in other words all users who can access workflows have access to the same set of them. |
#### Detailed Explanation of Object Permissions {#object-permissions}
| Type | Details |
| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Owner | By default objects are only visible and editable by their owner.<br/>Only the object owner can grant permissions to other users or groups.<br/>Additionally, only document owners can create share links and add / remove custom fields.<br/>For backwards compatibility objects can have no owner which makes them visible to any user. |
| View | Confers the ability to view (not edit) a document, tag, etc.<br/>Users without 'view' (or higher) permissions will be shown _'Private'_ in place of the object name for example when viewing a document with a tag for which the user doesn't have permissions. |
| Edit | Confers the ability to edit (and view) a document, tag, etc. |
### Password reset
In order to enable the password reset feature you will need to setup an SMTP backend, see
[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST)
[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST). If your installation does not have
[`PAPERLESS_URL`](configuration.md#PAPERLESS_URL) set, the reset link included in emails will use the server host.
## Consumption templates
## Workflows
Consumption templates were introduced in v2.0 and allow for finer control over what metadata (tags, doc
types) and permissions (owner, privileges) are assigned to documents during consumption. In general,
templates are applied sequentially (by sort order) but subsequent templates will never override an
assignment from a preceding template. The same is true for mail rules, e.g. if you set the correspondent
in a mail rule any subsequent consumption templates that are applied _will not_ overwrite this. The
exception to this is assignments that can be multiple e.g. tags and permissions, which will be merged.
!!! note
Consumption templates allow you to filter by:
v2.3 added "Workflows" and existing "Consumption Templates" were converted automatically to the new more powerful format.
Workflows allow hooking into the Paperless-ngx document pipeline, for example to alter what metadata (tags, doc types) and
permissions (owner, privileges) are assigned to documents. Workflows can have multiple 'triggers' and 'actions'. Triggers
are events (with optional filtering rules) that will cause the workflow to be run and actions are the set of sequential
actions to apply.
In general, workflows and any actions they contain are applied sequentially by sort order. For "assignment" actions, subsequent
workflow actions will override previous assignments, except for assignments that accept multiple items e.g. tags, custom
fields and permissions, which will be merged.
### Workflow Triggers
Currently, there are three events that correspond to workflow trigger 'types':
1. **Consumption Started**: _before_ a document is consumed, so events can include filters by source (mail, consumption
folder or API), file path, file name, mail rule
2. **Document Added**: _after_ a document is added. At this time, file path and source information is no longer available,
but the document content has been extracted and metadata such as document type, tags, etc. have been set, so these can now
be used for filtering.
3. **Document Updated**: when a document is updated. Similar to 'added' events, triggers can include filtering by content matching,
tags, doc type, or correspondent.
The following flow diagram illustrates the three trigger types:
```mermaid
flowchart TD
consumption{"Matching
'Consumption'
trigger(s)"}
added{"Matching
'Added'
trigger(s)"}
updated{"Matching
'Updated'
trigger(s)"}
A[New Document] --> consumption
consumption --> |Yes| C[Workflow Actions Run]
consumption --> |No| D
C --> D[Document Added]
D -- Paperless-ngx 'matching' of tags, etc. --> added
added --> |Yes| F[Workflow Actions Run]
added --> |No| G
F --> G[Document Finalized]
H[Existing Document Changed] --> updated
updated --> |Yes| J[Workflow Actions Run]
updated --> |No| K
J --> K[Document Saved]
```
#### Filters {#workflow-trigger-filters}
Workflows allow you to filter by:
- Source, e.g. documents uploaded via consume folder, API (& the web UI) and mail fetch
- File name, including wildcards e.g. \*.pdf will apply to all pdfs
- File path, including wildcards. Note that enabling `PAPERLESS_CONSUMER_RECURSIVE` would allow, for
example, automatically assigning documents to different owners based on the upload directory.
- Mail rule. Choosing this option will force 'mail fetch' to be the template source.
- Mail rule. Choosing this option will force 'mail fetch' to be the workflow source.
- Content matching (`Added` and `Updated` triggers only). Filter document content using the matching settings.
- Tags (`Added` and `Updated` triggers only). Filter for documents with any of the specified tags
- Document type (`Added` and `Updated` triggers only). Filter documents with this doc type
- Correspondent (`Added` and `Updated` triggers only). Filter documents with this correspondent
!!! note
### Workflow Actions
You must include a file name filter, a path filter or a mail rule filter. Use * for either to apply
to all files.
Consumption templates can assign:
There are currently two types of workflow actions, "Assignment", which can assign:
- Title, see [title placeholders](usage.md#title-placeholders) below
- Tags, correspondent, document types
- Tags, correspondent, document type and storage path
- Document owner
- View and / or edit permissions to users or groups
- Custom fields. Note that no value for the field will be set
### Consumption template permissions
and "Removal" actions, which can remove either all of or specific sets of the following:
All users who have application permissions for editing consumption templates can see the same set
of templates. In other words, templates themselves intentionally do not have an owner or permissions.
- Tags, correspondents, document types or storage paths
- Document owner
- View and / or edit permissions
- Custom fields
Given their potentially far-reaching capabilities, you may want to restrict access to templates.
#### Title placeholders
Upon migration, existing installs will grant access to consumption templates to users who can add
documents (and superusers who can always access all parts of the app).
### Title placeholders
Consumption template titles can include placeholders, _only for items that are assigned within the template_.
This is because at the time of consumption (when the title is to be set), no automatic tags etc. have been
applied. You can use the following placeholders:
Workflow titles can include placeholders but the available options differ depending on the type of
workflow trigger. This is because at the time of consumption (when the title is to be set), no automatic tags etc. have been
applied. You can use the following placeholders with any trigger type:
- `{correspondent}`: assigned correspondent name
- `{document_type}`: assigned document type name
@@ -311,6 +390,29 @@ applied. You can use the following placeholders:
- `{added_month_name}`: added month name
- `{added_month_name_short}`: added month short name
- `{added_day}`: added day
- `{added_time}`: added time in HH:MM format
- `{original_filename}`: original file name without extension
The following placeholders are only available for "added" or "updated" triggers
- `{created}`: created datetime
- `{created_year}`: created year
- `{created_year_short}`: created year
- `{created_month}`: created month
- `{created_month_name}`: created month name
- `{created_month_name_short}`: created month short name
- `{created_day}`: created day
- `{created_time}`: created time in HH:MM format
### Workflow permissions
All users who have application permissions for editing workflows can see the same set
of workflows. In other words, workflows themselves intentionally do not have an owner or permissions.
Given their potentially far-reaching capabilities, you may want to restrict access to workflows.
Upon migration, existing installs will grant access to workflows to users who can add
documents (and superusers who can always access all parts of the app).
## Custom Fields {#custom-fields}
@@ -342,12 +444,12 @@ The following custom field types are supported:
- `URL`: a valid url
- `Integer`: integer number e.g. 12
- `Number`: float number e.g. 12.3456
- `Monetary`: float number with exactly two decimals, e.g. 12.30
- `Document Link`: reference(s) to other document(s), displayed as links
- `Monetary`: [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) and a number with exactly two decimals, e.g. USD12.30
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
## Share Links
Paperless-ngx added the abiltiy to create shareable links to files in version 2.0. You can find the button for this on the document detail screen.
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}`.
@@ -358,6 +460,24 @@ Paperless-ngx added the abiltiy to create shareable links to files in version 2.
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 3 basic editing operations for PDFs (these operations cannot be performed on non-PDF files):
- Merging documents: available when selecting multiple documents for 'bulk editing'
- Rotating documents: available when selecting multiple documents for 'bulk editing' and from an individual document's details page.
- Splitting documents: available from an individual document's details page
!!! important
Note that rotation alters the Paperless-ngx _original_ file, which would, for example, invalidate a digital signature.
## Document History
As of version 2.7, Paperless-ngx automatically records all changes to a document and records this in an audit log. The feature requires [`PAPERLESS_AUDIT_LOG_ENABLED`](configuration.md#PAPERLESS_AUDIT_LOG_ENABLED) be enabled, which it is by default as of version 2.7.
Changes to documents are visible under the "History" tab. Note that certain changes such as those made by workflows, record the 'actor'
as "System".
## Best practices {#basic-searching}
Paperless offers a couple tools that help you organize your document
@@ -430,6 +550,16 @@ collection.
## Searching {#basic-usage_searching}
### Global search
The top search bar in the web UI performs a "global" search of the various
objects Paperless-ngx uses, including documents, tags, workflows, etc. Only
objects for which the user has appropriate permissions are returned. For
documents, if there are < 3 results, "advanced" search results (which use
the document index) will also be included. This can be disabled under settings.
### Document searches
Paperless offers an extensive searching mechanism that is designed to
allow you to quickly find a document you're looking for (for example,
that thing that just broke and you bought a couple months ago, that
@@ -485,6 +615,12 @@ language](https://whoosh.readthedocs.io/en/latest/querylang.html). For
details on what date parsing utilities are available, see [Date
parsing](https://whoosh.readthedocs.io/en/latest/dates.html#parsing-date-queries).
## Keyboard shortcuts / hotkeys
A list of available hotkeys can be shown on any page using <kbd>Shift</kbd> +
<kbd>?</kbd>. The help dialog shows only the keys that are currently available
based on which area of Paperless-ngx you are using.
## The recommended workflow {#usage-recommended-workflow}
Once you have familiarized yourself with paperless and are ready to use

View File

@@ -37,11 +37,11 @@ def worker_int(worker):
id2name = {th.ident: th.name for th in threading.enumerate()}
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name.get(threadId, ""), threadId))
code.append(f"\n# Thread: {id2name.get(threadId, '')}({threadId})")
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
code.append(f'File: "{filename}", line {lineno}, in {name}')
if line:
code.append(" %s" % (line.strip()))
code.append(f" {line.strip()}")
worker.log.debug("\n".join(code))

View File

@@ -56,8 +56,8 @@ if ! command -v docker &> /dev/null ; then
exit 1
fi
if ! command -v docker compose &> /dev/null ; then
echo "docker compose executable not found. Is docker compose installed?"
if ! docker compose &> /dev/null ; then
echo "docker compose plugin not found. Is docker compose installed?"
exit 1
fi
@@ -71,7 +71,17 @@ if ! docker stats --no-stream &> /dev/null ; then
sleep 3
fi
default_time_zone=$(timedatectl show -p Timezone --value)
# Added handling for timezone for busybox based linux, not having timedatectl available (i.e. QNAP QTS)
# if neither timedatectl nor /etc/TZ is succeeding, defaulting to GMT.
if command -v timedatectl &> /dev/null ; then
default_time_zone=$(timedatectl show -p Timezone --value)
elif [ -f /etc/TZ ] && [ -f /etc/tzlist ] ; then
TZ=$(cat /etc/TZ)
default_time_zone=$(grep -B 1 -m 1 "$TZ" /etc/tzlist | head -1 | cut -f 2 -d =)
else
echo "WARN: unable to detect timezone, defaulting to Etc/UTC"
default_time_zone="Etc/UTC"
fi
set -e
@@ -315,7 +325,7 @@ fi
wget "https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.$DOCKER_COMPOSE_VERSION.yml" -O docker-compose.yml
wget "https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.env" -O .env
SECRET_KEY=$(LC_ALL=C tr -dc 'a-zA-Z0-9!"#$%&'\''()*+,-./:;<=>?@[\]^_`{|}~' < /dev/urandom | head --bytes 64)
SECRET_KEY=$(LC_ALL=C tr -dc 'a-zA-Z0-9!#$%&()*+,-./:;<=>?@[\]^_`{|}~' < /dev/urandom | dd bs=1 count=64 2>/dev/null)
DEFAULT_LANGUAGES=("deu eng fra ita spa")
@@ -335,7 +345,7 @@ read -r -a OCR_LANGUAGES_ARRAY <<< "${_split_langs}"
fi
echo "PAPERLESS_TIME_ZONE=$TIME_ZONE"
echo "PAPERLESS_OCR_LANGUAGE=$OCR_LANGUAGE"
echo "PAPERLESS_SECRET_KEY=$SECRET_KEY"
echo "PAPERLESS_SECRET_KEY='$SECRET_KEY'"
if [[ ! ${DEFAULT_LANGUAGES[*]} =~ ${OCR_LANGUAGES_ARRAY[*]} ]] ; then
echo "PAPERLESS_OCR_LANGUAGES=${OCR_LANGUAGES_ARRAY[*]}"
fi
@@ -380,7 +390,7 @@ fi
docker compose pull
if [ "$DATABASE_BACKEND" == "postgres" ] || [ "$DATABASE_BACKEND" == "mariadb" ] ; then
echo "Starting DB first for initilzation"
echo "Starting DB first for initialization"
docker compose up --detach db
# hopefully enough time for even the slower systems
sleep 15

View File

@@ -44,6 +44,11 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.snippets
- footnotes
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
strict: true
nav:
- index.md
@@ -68,4 +73,6 @@ extra:
link: https://matrix.to/#/#paperless:matrix.org
plugins:
- search
- glightbox
- glightbox:
skip_classes:
- no-lightbox

View File

@@ -0,0 +1,36 @@
{
"folders": [
{
"path": "."
},
{
"path": "./src",
"name": "Backend"
},
{
"path": "./src-ui",
"name": "Frontend"
},
{
"path": "./.github",
"name": "CI/CD"
},
{
"path": "./docs",
"name": "Documentation"
}
],
"settings": {
"files.exclude": {
"**/__pycache__": true,
"**/.mypy_cache": true,
"**/.ruff_cache": true,
"**/.pytest_cache": true,
"**/.idea": true,
"**/.venv": true,
"**/.coverage": true,
"**/coverage.json": true
}
}
}

View File

@@ -68,6 +68,8 @@
#PAPERLESS_CONSUMER_BARCODE_STRING=PATCHT
#PAPERLESS_CONSUMER_BARCODE_UPSCALE=0.0
#PAPERLESS_CONSUMER_BARCODE_DPI=300
#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=false
#PAPERLESS_CONSUMER_TAG_BARCODE_MAPPING={"TAG:(.*)": "\\g<1>"}
#PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED=false
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_SUBDIR_NAME=double-sided
#PAPERLESS_CONSUMER_COLLATE_DOUBLE_SIDED_TIFF_SUPPORT=false

View File

@@ -31,6 +31,7 @@
"fr-FR": "src/locale/messages.fr_FR.xlf",
"hu-HU": "src/locale/messages.hu_HU.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"ja-JP": "src/locale/messages.ja_JP.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"no-NO": "src/locale/messages.no_NO.xlf",
@@ -76,7 +77,9 @@
"scripts": [],
"allowedCommonJsDependencies": [
"pdfjs-dist",
"pdfjs-dist/web/pdf_viewer"
"pdfjs-dist/web/pdf_viewer",
"filesize",
"file-saver"
],
"vendorChunk": true,
"extractLicenses": false,
@@ -125,18 +128,18 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build:en-US"
"buildTarget": "paperless-ui:build:en-US"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
"buildTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
"buildTarget": "paperless-ui:build"
}
},
"test": {

View File

@@ -124,7 +124,7 @@
"content": {
"size": -1,
"mimeType": "application/json",
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,

View File

@@ -124,7 +124,7 @@
"content": {
"size": -1,
"mimeType": "application/json",
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,

View File

@@ -124,7 +124,7 @@
"content": {
"size": -1,
"mimeType": "application/json",
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,

View File

@@ -124,7 +124,7 @@
"content": {
"size": -1,
"mimeType": "application/json",
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true}]}"
"text": "{\"count\":6,\"next\":null,\"previous\":null,\"all\":[8,17,7,4,11,15],\"results\":[{\"id\":8,\"name\":\"Correspondent 2\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":3,\"value\":\"2\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":17,\"name\":\"In the Last Month\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":20,\"value\":\"created:[-1 month to now]\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":7,\"name\":\"Inbox\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"9\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":4,\"name\":\"Recently Added\",\"show_on_dashboard\":true,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":11,\"name\":\"Tag: Another Sample Tag\",\"show_on_dashboard\":false,\"show_in_sidebar\":true,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":6,\"value\":\"4\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]},{\"id\":15,\"name\":\"View ASN not empty\",\"show_on_dashboard\":false,\"show_in_sidebar\":false,\"sort_field\":\"created\",\"sort_reverse\":true,\"filter_rules\":[{\"rule_type\":18,\"value\":\"false\"}],\"owner\":\"2\",\"user_can_change\":true,\"page_size\":10,\"display_mode\":\"table\",\"display_fields\":[\"created\",\"title\",\"tag\",\"documenttype\"]}]}"
},
"headersSize": -1,
"bodySize": -1,

View File

@@ -12,13 +12,9 @@ test('should activate / deactivate save button when changes are saved', async ({
await expect(page.getByTitle('Storage path', { exact: true })).toHaveText(
/\w+/
)
await expect(
page.getByRole('button', { name: 'Save', exact: true })
).toBeDisabled()
await expect(page.getByRole('button', { name: 'Save' }).nth(1)).toBeDisabled()
await page.getByTitle('Storage path').getByTitle('Clear all').click()
await expect(
page.getByRole('button', { name: 'Save', exact: true })
).toBeEnabled()
await expect(page.getByRole('button', { name: 'Save' }).nth(1)).toBeEnabled()
})
test('should warn on unsaved changes', async ({ page }) => {
@@ -27,16 +23,12 @@ test('should warn on unsaved changes', async ({ page }) => {
await expect(page.getByTitle('Correspondent', { exact: true })).toHaveText(
/\w+/
)
await expect(
page.getByRole('button', { name: 'Save', exact: true })
).toBeDisabled()
await expect(page.getByRole('button', { name: 'Save' }).nth(1)).toBeDisabled()
await page
.getByTitle('Storage path', { exact: true })
.getByTitle('Clear all')
.click()
await expect(
page.getByRole('button', { name: 'Save', exact: true })
).toBeEnabled()
await expect(page.getByRole('button', { name: 'Save' }).nth(1)).toBeEnabled()
await page.getByRole('button', { name: 'Close', exact: true }).click()
await expect(page.getByRole('dialog')).toHaveText(/unsaved changes/)
await page.getByRole('button', { name: 'Cancel' }).click()

View File

@@ -45,8 +45,8 @@ test('basic filtering', async ({ page }) => {
test('text filtering', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR2, { notFound: 'fallback' })
await page.goto('/documents')
await page.getByRole('textbox').click()
await page.getByRole('textbox').fill('test')
await page.getByRole('main').getByRole('combobox').click()
await page.getByRole('main').getByRole('combobox').fill('test')
await expect(page.locator('pngx-document-list')).toHaveText(/32 documents/)
await expect(page).toHaveURL(/title_content=test/)
await page.getByRole('button', { name: 'Title & content' }).click()
@@ -59,12 +59,12 @@ test('text filtering', async ({ page }) => {
await expect(page.locator('pngx-document-list')).toHaveText(/26 documents/)
await page.getByRole('button', { name: 'Advanced search' }).click()
await page.getByRole('button', { name: 'ASN' }).click()
await page.getByRole('textbox').fill('1123')
await page.getByRole('main').getByRole('combobox').nth(1).fill('1123')
await expect(page).toHaveURL(/archive_serial_number=1123/)
await expect(page.locator('pngx-document-list')).toHaveText(/one document/i)
await page.locator('select').selectOption('greater')
await page.getByRole('textbox').click()
await page.getByRole('textbox').fill('1123')
await page.getByRole('main').getByRole('combobox').nth(1).click()
await page.getByRole('main').getByRole('combobox').nth(1).fill('1123')
await expect(page).toHaveURL(/archive_serial_number__gt=1123/)
await expect(page.locator('pngx-document-list')).toHaveText(/5 documents/)
await page.locator('select').selectOption('less')
@@ -81,14 +81,15 @@ test('text filtering', async ({ page }) => {
test('date filtering', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR3, { notFound: 'fallback' })
await page.goto('/documents')
await page.getByRole('button', { name: 'Created' }).click()
await page.getByRole('menuitem', { name: 'Last 3 months' }).click()
await page.getByRole('button', { name: 'Dates' }).click()
await page.getByRole('menuitem', { name: 'Last 3 months' }).first().click()
await expect(page.locator('pngx-document-list')).toHaveText(/one document/i)
await page.getByRole('button', { name: 'Created Clear selected' }).click()
await page.getByRole('button', { name: 'Created' }).click()
await page.getByRole('button', { name: 'Dates Clear selected' }).click()
await page.getByRole('button', { name: 'Dates' }).click()
await page
.getByRole('menuitem', { name: 'After mm/dd/yyyy' })
.getByRole('button')
.first()
.click()
await page.getByRole('combobox', { name: 'Select month' }).selectOption('12')
await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022')
@@ -131,18 +132,18 @@ test('sorting', async ({ page }) => {
await page.getByRole('button', { name: 'Notes' }).click()
await expect(page).toHaveURL(/sort=num_notes/)
await page.getByRole('button', { name: 'Sort' }).click()
await page.locator('.w-100 > label > .toolbaricon').first().click()
await page.locator('.w-100 > label > i-bs').first().click()
await expect(page).not.toHaveURL(/reverse=1/)
})
test('change views', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR5, { notFound: 'fallback' })
await page.goto('/documents')
await page.locator('pngx-page-header label').first().click()
await page.locator('.btn-group label').first().click()
await expect(page.locator('pngx-document-list table')).toBeVisible()
await page.locator('pngx-page-header label').nth(1).click()
await page.locator('.btn-group label').nth(1).click()
await expect(page.locator('pngx-document-card-small').first()).toBeAttached()
await page.locator('pngx-page-header label').nth(2).click()
await page.locator('.btn-group label').nth(2).click()
await expect(page.locator('pngx-document-card-large').first()).toBeAttached()
})

File diff suppressed because it is too large Load Diff

7101
src-ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,56 +11,58 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "^16.2.11",
"@angular/common": "~16.2.11",
"@angular/compiler": "~16.2.11",
"@angular/core": "~16.2.11",
"@angular/forms": "~16.2.11",
"@angular/localize": "~16.2.11",
"@angular/platform-browser": "~16.2.11",
"@angular/platform-browser-dynamic": "~16.2.11",
"@angular/router": "~16.2.11",
"@ng-bootstrap/ng-bootstrap": "^15.1.2",
"@ng-select/ng-select": "^11.2.0",
"@angular/cdk": "^17.3.6",
"@angular/common": "~17.3.7",
"@angular/compiler": "~17.3.7",
"@angular/core": "~17.3.7",
"@angular/forms": "~17.3.7",
"@angular/localize": "~17.3.7",
"@angular/platform-browser": "~17.3.7",
"@angular/platform-browser-dynamic": "~17.3.7",
"@angular/router": "~17.3.7",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ng-select/ng-select": "^12.0.7",
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2",
"bootstrap": "^5.3.3",
"file-saver": "^2.0.5",
"mime-names": "^1.0.0",
"ngx-bootstrap-icons": "^1.9.3",
"ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1",
"ngx-cookie-service": "^17.1.0",
"ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.6",
"ngx-filesize": "^3.0.3",
"ngx-ui-tour-ng-bootstrap": "^14.0.2",
"pdfjs-dist": "^3.11.174",
"rxjs": "^7.8.1",
"tslib": "^2.6.2",
"uuid": "^9.0.1",
"zone.js": "^0.13.3"
"zone.js": "^0.14.4"
},
"devDependencies": {
"@angular-builders/jest": "16.0.1",
"@angular-devkit/build-angular": "~16.2.9",
"@angular-eslint/builder": "16.2.0",
"@angular-eslint/eslint-plugin": "16.2.0",
"@angular-eslint/eslint-plugin-template": "16.2.0",
"@angular-eslint/schematics": "16.2.0",
"@angular-eslint/template-parser": "16.2.0",
"@angular/cli": "~16.2.9",
"@angular/compiler-cli": "~16.2.3",
"@playwright/test": "^1.40.1",
"@types/jest": "^29.5.10",
"@types/node": "^20.10.2",
"@typescript-eslint/eslint-plugin": "^6.13.1",
"@typescript-eslint/parser": "^6.13.1",
"@angular-builders/jest": "17.0.3",
"@angular-devkit/build-angular": "~17.3.6",
"@angular-eslint/builder": "17.3.0",
"@angular-eslint/eslint-plugin": "17.3.0",
"@angular-eslint/eslint-plugin-template": "17.3.0",
"@angular-eslint/schematics": "17.3.0",
"@angular-eslint/template-parser": "17.3.0",
"@angular/cli": "~17.3.6",
"@angular/compiler-cli": "~17.3.2",
"@playwright/test": "^1.42.1",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.2",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"concurrently": "^8.2.2",
"eslint": "^8.55.0",
"eslint": "^8.57.0",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-preset-angular": "^13.1.4",
"jest-preset-angular": "^14.0.0",
"jest-websocket-mock": "^2.5.0",
"patch-package": "^8.0.0",
"ts-node": "~10.9.1",
"typescript": "^5.1.6",
"typescript": "^5.3.3",
"wait-on": "^7.2.0"
}
}

View File

@@ -23,6 +23,7 @@ import localeFi from '@angular/common/locales/fi'
import localeFr from '@angular/common/locales/fr'
import localeHu from '@angular/common/locales/hu'
import localeIt from '@angular/common/locales/it'
import localeJa from '@angular/common/locales/ja'
import localeLb from '@angular/common/locales/lb'
import localeNl from '@angular/common/locales/nl'
import localeNo from '@angular/common/locales/no'
@@ -53,6 +54,7 @@ registerLocaleData(localeFi)
registerLocaleData(localeFr)
registerLocaleData(localeHu)
registerLocaleData(localeIt)
registerLocaleData(localeJa)
registerLocaleData(localeLb)
registerLocaleData(localeNl)
registerLocaleData(localeNo)
@@ -74,12 +76,16 @@ const mock = () => {
let storage: { [key: string]: string } = {}
return {
getItem: (key: string) => (key in storage ? storage[key] : null),
setItem: (key: string, value: string) => (storage[key] = value || ''),
setItem: (key: string, value: string) => {
if (value.length > 1000000) throw new Error('localStorage overflow')
storage[key] = value || ''
},
removeItem: (key: string) => delete storage[key],
clear: () => (storage = {}),
}
}
Object.defineProperty(window, 'open', { value: jest.fn() })
Object.defineProperty(window, 'localStorage', { value: mock() })
Object.defineProperty(window, 'sessionStorage', { value: mock() })
Object.defineProperty(window, 'getComputedStyle', {
@@ -92,6 +98,10 @@ Object.defineProperty(navigator, 'clipboard', {
})
Object.defineProperty(navigator, 'canShare', { value: () => true })
Object.defineProperty(window, 'ResizeObserver', { value: mock() })
Object.defineProperty(window, 'location', {
configurable: true,
value: { reload: jest.fn() },
})
HTMLCanvasElement.prototype.getContext = <
typeof HTMLCanvasElement.prototype.getContext

View File

@@ -21,10 +21,11 @@ import {
PermissionAction,
PermissionType,
} from './services/permissions.service'
import { ConsumptionTemplatesComponent } from './components/manage/consumption-templates/consumption-templates.component'
import { WorkflowsComponent } from './components/manage/workflows/workflows.component'
import { MailComponent } from './components/manage/mail/mail.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
import { ConfigComponent } from './components/admin/config/config.component'
export const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
@@ -140,10 +141,7 @@ export const routes: Routes = [
component: LogsComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Admin,
},
requireAdmin: true,
},
},
// redirect old paths
@@ -162,7 +160,7 @@ export const routes: Routes = [
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
action: PermissionAction.Change,
type: PermissionType.UISettings,
},
},
@@ -179,6 +177,17 @@ export const routes: Routes = [
},
},
},
{
path: 'config',
component: ConfigComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.Change,
type: PermissionType.AppConfig,
},
},
},
{
path: 'tasks',
component: TasksComponent,
@@ -202,13 +211,13 @@ export const routes: Routes = [
},
},
{
path: 'templates',
component: ConsumptionTemplatesComponent,
path: 'workflows',
component: WorkflowsComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.ConsumptionTemplate,
type: PermissionType.Workflow,
},
},
},

View File

@@ -1,32 +1,36 @@
<pngx-toasts></pngx-toasts>
<pngx-file-drop>
<ng-container content>
<router-outlet></router-outlet>
</ng-container>
<ng-container content>
<router-outlet></router-outlet>
</ng-container>
</pngx-file-drop>
<tour-step-template>
<ng-template #tourStep let-step="step">
<p class="tour-step-content" [innerHTML]="step?.content"></p>
<hr/>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-light text-dark">{{ tourService.steps?.indexOf(step) + 1 }} / {{ tourService.steps?.length }}</span>
<div class="tour-step-navigation btn-toolbar" role="toolbar" aria-label="Controls">
<div class="btn-group btn-group-sm me-2" role="group" aria-label="Dismiss">
<button class="btn btn-outline-danger" (click)="tourService.end()">
{{ step?.endBtnTitle }}
</button>
</div>
<div class="btn-group btn-group-sm align-self-end" role="group" aria-label="Previous / Next">
<button *ngIf="tourService.hasPrev(step)" class="btn btn-outline-primary" (click)="tourService.prev()">
« {{ step?.prevBtnTitle }}
</button>
<button *ngIf="tourService.hasNext(step)" class="btn btn-outline-primary" (click)="tourService.next()">
{{ step?.nextBtnTitle }} »
</button>
</div>
</div>
<ng-template #tourStep let-step="step">
<p class="tour-step-content" [innerHTML]="step?.content"></p>
<hr/>
<div class="d-flex justify-content-between align-items-center">
<span class="badge bg-light text-dark">{{ tourService.steps?.indexOf(step) + 1 }} / {{ tourService.steps?.length }}</span>
<div class="tour-step-navigation btn-toolbar" role="toolbar" aria-label="Controls">
<div class="btn-group btn-group-sm me-2" role="group" aria-label="Dismiss">
<button class="btn btn-outline-danger" (click)="tourService.end()">
{{ step?.endBtnTitle }}
</button>
</div>
</ng-template>
<div class="btn-group btn-group-sm align-self-end" role="group" aria-label="Previous / Next">
@if (tourService.hasPrev(step)) {
<button class="btn btn-outline-primary" (click)="tourService.prev()">
« {{ step?.prevBtnTitle }}
</button>
}
@if (tourService.hasNext(step)) {
<button class="btn btn-outline-primary" (click)="tourService.next()">
{{ step?.nextBtnTitle }} »
</button>
}
</div>
</div>
</div>
</ng-template>
</tour-step-template>

View File

@@ -5,8 +5,7 @@ import {
fakeAsync,
tick,
} from '@angular/core/testing'
import { Router } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import { Router, RouterModule } from '@angular/router'
import { TourService, TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import { Subject } from 'rxjs'
import { routes } from './app-routing.module'
@@ -21,6 +20,10 @@ import { ToastService, Toast } from './services/toast.service'
import { SettingsService } from './services/settings.service'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { NgxFileDropModule } from 'ngx-file-drop'
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { HotKeyService } from './services/hot-key.service'
import { PermissionsGuard } from './guards/permissions.guard'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
describe('AppComponent', () => {
let component: AppComponent
@@ -31,16 +34,18 @@ describe('AppComponent', () => {
let toastService: ToastService
let router: Router
let settingsService: SettingsService
let hotKeyService: HotKeyService
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [AppComponent, ToastsComponent, FileDropComponent],
providers: [],
providers: [PermissionsGuard, DirtySavedViewGuard],
imports: [
HttpClientTestingModule,
TourNgBootstrapModule,
RouterTestingModule.withRoutes(routes),
RouterModule.forRoot(routes),
NgxFileDropModule,
NgbModalModule,
],
}).compileComponents()
@@ -50,6 +55,7 @@ describe('AppComponent', () => {
settingsService = TestBed.inject(SettingsService)
toastService = TestBed.inject(ToastService)
router = TestBed.inject(Router)
hotKeyService = TestBed.inject(HotKeyService)
fixture = TestBed.createComponent(AppComponent)
component = fixture.componentInstance
})
@@ -139,4 +145,20 @@ describe('AppComponent', () => {
fileStatusSubject.next(new FileStatus())
expect(toastSpy).toHaveBeenCalled()
})
it('should support hotkeys', () => {
const addShortcutSpy = jest.spyOn(hotKeyService, 'addShortcut')
const routerSpy = jest.spyOn(router, 'navigate')
// prevent actual navigation
routerSpy.mockReturnValue(new Promise(() => {}))
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
component.ngOnInit()
expect(addShortcutSpy).toHaveBeenCalled()
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'h' }))
expect(routerSpy).toHaveBeenCalledWith(['/dashboard'])
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'd' }))
expect(routerSpy).toHaveBeenCalledWith(['/documents'])
document.dispatchEvent(new KeyboardEvent('keydown', { key: 's' }))
expect(routerSpy).toHaveBeenCalledWith(['/settings'])
})
})

View File

@@ -1,5 +1,5 @@
import { SettingsService } from './services/settings.service'
import { SETTINGS_KEYS } from './data/paperless-uisettings'
import { SETTINGS_KEYS } from './data/ui-settings'
import { Component, OnDestroy, OnInit, Renderer2 } from '@angular/core'
import { Router } from '@angular/router'
import { Subscription, first } from 'rxjs'
@@ -12,6 +12,7 @@ import {
PermissionsService,
PermissionType,
} from './services/permissions.service'
import { HotKeyService } from './services/hot-key.service'
@Component({
selector: 'pngx-root',
@@ -31,10 +32,9 @@ export class AppComponent implements OnInit, OnDestroy {
private tasksService: TasksService,
public tourService: TourService,
private renderer: Renderer2,
private permissionsService: PermissionsService
private permissionsService: PermissionsService,
private hotKeyService: HotKeyService
) {
let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
this.settings.updateAppearanceSettings()
}
@@ -125,6 +125,36 @@ export class AppComponent implements OnInit, OnDestroy {
}
})
this.hotKeyService
.addShortcut({ keys: 'h', description: $localize`Dashboard` })
.subscribe(() => {
this.router.navigate(['/dashboard'])
})
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.Document
)
) {
this.hotKeyService
.addShortcut({ keys: 'd', description: $localize`Documents` })
.subscribe(() => {
this.router.navigate(['/documents'])
})
}
if (
this.permissionsService.currentUserCan(
PermissionAction.Change,
PermissionType.UISettings
)
) {
this.hotKeyService
.addShortcut({ keys: 's', description: $localize`Settings` })
.subscribe(() => {
this.router.navigate(['/settings'])
})
}
const prevBtnTitle = $localize`Prev`
const nextBtnTitle = $localize`Next`
const endBtnTitle = $localize`End`
@@ -178,9 +208,9 @@ export class AppComponent implements OnInit, OnDestroy {
},
},
{
anchorId: 'tour.consumption-templates',
content: $localize`Consumption templates give you finer control over the document ingestion process.`,
route: '/templates',
anchorId: 'tour.workflows',
content: $localize`Workflows give you more control over the document pipeline.`,
route: '/workflows',
backdropConfig: {
offset: 0,
},

View File

@@ -31,7 +31,7 @@ import { ToastsComponent } from './components/common/toasts/toasts.component'
import { FilterEditorComponent } from './components/document-list/filter-editor/filter-editor.component'
import { FilterableDropdownComponent } from './components/common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableDropdownButtonComponent } from './components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { DateDropdownComponent } from './components/common/date-dropdown/date-dropdown.component'
import { DatesDropdownComponent } from './components/common/dates-dropdown/dates-dropdown.component'
import { DocumentCardLargeComponent } from './components/document-list/document-card-large/document-card-large.component'
import { DocumentCardSmallComponent } from './components/document-list/document-card-small/document-card-small.component'
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component'
@@ -95,8 +95,8 @@ import { UsernamePipe } from './pipes/username.pipe'
import { LogoComponent } from './components/common/logo/logo.component'
import { IsNumberPipe } from './pipes/is-number.pipe'
import { ShareLinksDropdownComponent } from './components/common/share-links-dropdown/share-links-dropdown.component'
import { ConsumptionTemplatesComponent } from './components/manage/consumption-templates/consumption-templates.component'
import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
import { WorkflowsComponent } from './components/manage/workflows/workflows.component'
import { WorkflowEditDialogComponent } from './components/common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
import { MailComponent } from './components/manage/mail/mail.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { DragDropModule } from '@angular/cdk/drag-drop'
@@ -107,6 +107,220 @@ import { CustomFieldsDropdownComponent } from './components/common/custom-fields
import { ProfileEditDialogComponent } from './components/common/profile-edit-dialog/profile-edit-dialog.component'
import { PdfViewerComponent } from './components/common/pdf-viewer/pdf-viewer.component'
import { DocumentLinkComponent } from './components/common/input/document-link/document-link.component'
import { PreviewPopupComponent } from './components/common/preview-popup/preview-popup.component'
import { SwitchComponent } from './components/common/input/switch/switch.component'
import { ConfigComponent } from './components/admin/config/config.component'
import { FileComponent } from './components/common/input/file/file.component'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { ConfirmButtonComponent } from './components/common/confirm-button/confirm-button.component'
import { MonetaryComponent } from './components/common/input/monetary/monetary.component'
import { SystemStatusDialogComponent } from './components/common/system-status-dialog/system-status-dialog.component'
import { NgxFilesizeModule } from 'ngx-filesize'
import { RotateConfirmDialogComponent } from './components/common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
import { MergeConfirmDialogComponent } from './components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
import { DocumentHistoryComponent } from './components/document-history/document-history.component'
import { DragDropSelectComponent } from './components/common/input/drag-drop-select/drag-drop-select.component'
import { CustomFieldDisplayComponent } from './components/common/custom-field-display/custom-field-display.component'
import { GlobalSearchComponent } from './components/app-frame/global-search/global-search.component'
import { HotkeyDialogComponent } from './components/common/hotkey-dialog/hotkey-dialog.component'
import {
airplane,
archive,
arrowClockwise,
arrowCounterclockwise,
arrowDown,
arrowLeft,
arrowRepeat,
arrowRight,
arrowRightShort,
arrowUpRight,
asterisk,
bodyText,
boxArrowUp,
boxArrowUpRight,
boxes,
calendar,
calendarEvent,
calendarEventFill,
cardChecklist,
cardHeading,
caretDown,
caretUp,
chatLeftText,
check,
check2All,
checkAll,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
clipboard,
clipboardCheck,
clipboardCheckFill,
clipboardFill,
dash,
diagram3,
dice5,
doorOpen,
download,
envelope,
envelopeAt,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkCheck,
fileEarmarkFill,
fileEarmarkLock,
files,
fileText,
filter,
folder,
folderFill,
funnel,
gear,
grid,
gripVertical,
hash,
hddStack,
house,
infoCircle,
journals,
link,
listTask,
listUl,
pencil,
people,
peopleFill,
person,
personCircle,
personFill,
personFillLock,
personLock,
personSquare,
plus,
plusCircle,
questionCircle,
scissors,
search,
slashCircle,
sliders2Vertical,
sortAlphaDown,
sortAlphaUpAlt,
tagFill,
tag,
tags,
textIndentLeft,
textLeft,
threeDots,
threeDotsVertical,
trash,
uiRadios,
upcScan,
x,
xLg,
} from 'ngx-bootstrap-icons'
const icons = {
airplane,
archive,
arrowClockwise,
arrowCounterclockwise,
arrowDown,
arrowLeft,
arrowRepeat,
arrowRight,
arrowRightShort,
arrowUpRight,
asterisk,
bodyText,
boxArrowUp,
boxArrowUpRight,
boxes,
calendar,
calendarEvent,
calendarEventFill,
cardChecklist,
cardHeading,
caretDown,
caretUp,
chatLeftText,
check,
check2All,
checkAll,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
clipboard,
clipboardCheck,
clipboardCheckFill,
clipboardFill,
dash,
diagram3,
dice5,
doorOpen,
download,
envelope,
envelopeAt,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkCheck,
fileEarmarkFill,
fileEarmarkLock,
files,
fileText,
filter,
folder,
folderFill,
funnel,
gear,
grid,
gripVertical,
hash,
hddStack,
house,
infoCircle,
journals,
link,
listTask,
listUl,
pencil,
people,
peopleFill,
person,
personCircle,
personFill,
personFillLock,
personLock,
personSquare,
plus,
plusCircle,
questionCircle,
scissors,
search,
slashCircle,
sliders2Vertical,
sortAlphaDown,
sortAlphaUpAlt,
tagFill,
tag,
tags,
textIndentLeft,
textLeft,
threeDots,
threeDotsVertical,
trash,
uiRadios,
upcScan,
x,
xLg,
}
import localeAf from '@angular/common/locales/af'
import localeAr from '@angular/common/locales/ar'
@@ -123,6 +337,7 @@ import localeFi from '@angular/common/locales/fi'
import localeFr from '@angular/common/locales/fr'
import localeHu from '@angular/common/locales/hu'
import localeIt from '@angular/common/locales/it'
import localeJa from '@angular/common/locales/ja'
import localeLb from '@angular/common/locales/lb'
import localeNl from '@angular/common/locales/nl'
import localeNo from '@angular/common/locales/no'
@@ -153,6 +368,7 @@ registerLocaleData(localeFi)
registerLocaleData(localeFr)
registerLocaleData(localeHu)
registerLocaleData(localeIt)
registerLocaleData(localeJa)
registerLocaleData(localeLb)
registerLocaleData(localeNl)
registerLocaleData(localeNo)
@@ -201,7 +417,7 @@ function initializeApp(settings: SettingsService) {
FilterEditorComponent,
FilterableDropdownComponent,
ToggleableDropdownButtonComponent,
DateDropdownComponent,
DatesDropdownComponent,
DocumentCardLargeComponent,
DocumentCardSmallComponent,
BulkEditorComponent,
@@ -250,8 +466,8 @@ function initializeApp(settings: SettingsService) {
LogoComponent,
IsNumberPipe,
ShareLinksDropdownComponent,
ConsumptionTemplatesComponent,
ConsumptionTemplateEditDialogComponent,
WorkflowsComponent,
WorkflowEditDialogComponent,
MailComponent,
UsersAndGroupsComponent,
FileDropComponent,
@@ -261,6 +477,21 @@ function initializeApp(settings: SettingsService) {
ProfileEditDialogComponent,
PdfViewerComponent,
DocumentLinkComponent,
PreviewPopupComponent,
SwitchComponent,
ConfigComponent,
FileComponent,
ConfirmButtonComponent,
MonetaryComponent,
SystemStatusDialogComponent,
RotateConfirmDialogComponent,
MergeConfirmDialogComponent,
SplitConfirmDialogComponent,
DocumentHistoryComponent,
DragDropSelectComponent,
CustomFieldDisplayComponent,
GlobalSearchComponent,
HotkeyDialogComponent,
],
imports: [
BrowserModule,
@@ -274,6 +505,8 @@ function initializeApp(settings: SettingsService) {
ColorSliderModule,
TourNgBootstrapModule,
DragDropModule,
NgxBootstrapIconsModule.pick(icons),
NgxFilesizeModule,
],
providers: [
{

View File

@@ -0,0 +1,59 @@
<pngx-page-header
title="Application Configuration"
i18n-title
info="Global app configuration options which apply to <strong>every</strong> user of this install of Paperless-ngx. Options can also be set using environment variables or the configuration file but the value here will always take precedence."
i18n-info
infoLink="configuration">
</pngx-page-header>
<form [formGroup]="configForm" (ngSubmit)="saveConfig()" class="pb-4">
<ul ngbNav #nav="ngbNav" class="nav-tabs">
@for (category of optionCategories; track category) {
<li [ngbNavItem]="category">
<a ngbNavLink i18n>{{category}}</a>
<ng-template ngbNavContent>
<div class="p-3">
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
@for (option of getCategoryOptions(category); track option.key) {
<div class="col">
<div class="card bg-light">
<div class="card-body">
<div class="card-title">
<h6>
{{option.title}}
<a class="btn btn-sm btn-link" title="Read the documentation about this setting" i18n-title [href]="getDocsUrl(option.config_key)" target="_blank" referrerpolicy="no-referrer">
<i-bs name="info-circle"></i-bs>
</a>
</h6>
</div>
<div class="mb-n3">
@switch (option.type) {
@case (ConfigOptionType.Select) { <pngx-input-select [formControlName]="option.key" [error]="errors[option.key]" [items]="option.choices" [allowNull]="true"></pngx-input-select> }
@case (ConfigOptionType.Number) { <pngx-input-number [formControlName]="option.key" [error]="errors[option.key]" [showAdd]="false"></pngx-input-number> }
@case (ConfigOptionType.Boolean) { <pngx-input-switch [formControlName]="option.key" [error]="errors[option.key]" [showUnsetNote]="true" [horizontal]="true" title="Enable" i18n-title></pngx-input-switch> }
@case (ConfigOptionType.String) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
@case (ConfigOptionType.JSON) { <pngx-input-text [formControlName]="option.key" [error]="errors[option.key]"></pngx-input-text> }
@case (ConfigOptionType.File) { <pngx-input-file [formControlName]="option.key" (upload)="uploadFile($event, option.key)" [error]="errors[option.key]"></pngx-input-file> }
}
</div>
</div>
</div>
</div>
}
</div>
</div>
</ng-template>
</li>
}
</ul>
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<div class="btn-toolbar" role="toolbar">
<div class="btn-group me-2">
<button type="button" (click)="discardChanges()" class="btn btn-secondary" [disabled]="loading || (isDirty$ | async) === false" i18n>Discard</button>
</div>
<div class="btn-group">
<button type="submit" class="btn btn-primary" [disabled]="loading || !configForm.valid || (isDirty$ | async) === false" i18n>Save</button>
</div>
</div>
</form>

View File

@@ -0,0 +1,145 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ConfigComponent } from './config.component'
import { ConfigService } from 'src/app/services/config.service'
import { ToastService } from 'src/app/services/toast.service'
import { of, throwError } from 'rxjs'
import { OutputTypeConfig } from 'src/app/data/paperless-config'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { BrowserModule } from '@angular/platform-browser'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { TextComponent } from '../../common/input/text/text.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { SwitchComponent } from '../../common/input/switch/switch.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { FileComponent } from '../../common/input/file/file.component'
import { SettingsService } from 'src/app/services/settings.service'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
describe('ConfigComponent', () => {
let component: ConfigComponent
let fixture: ComponentFixture<ConfigComponent>
let configService: ConfigService
let toastService: ToastService
let settingService: SettingsService
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
ConfigComponent,
TextComponent,
SelectComponent,
NumberComponent,
SwitchComponent,
FileComponent,
PageHeaderComponent,
],
imports: [
HttpClientTestingModule,
BrowserModule,
NgbModule,
NgSelectModule,
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule.pick(allIcons),
],
}).compileComponents()
configService = TestBed.inject(ConfigService)
toastService = TestBed.inject(ToastService)
settingService = TestBed.inject(SettingsService)
fixture = TestBed.createComponent(ConfigComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should load config on init, show error if necessary', () => {
const getSpy = jest.spyOn(configService, 'getConfig')
const errorSpy = jest.spyOn(toastService, 'showError')
getSpy.mockReturnValueOnce(
throwError(() => new Error('Error getting config'))
)
component.ngOnInit()
expect(getSpy).toHaveBeenCalled()
expect(errorSpy).toHaveBeenCalled()
getSpy.mockReturnValueOnce(
of({ output_type: OutputTypeConfig.PDF_A } as any)
)
component.ngOnInit()
expect(component.initialConfig).toEqual({
output_type: OutputTypeConfig.PDF_A,
})
})
it('should save config, show error if necessary', () => {
const saveSpy = jest.spyOn(configService, 'saveConfig')
const errorSpy = jest.spyOn(toastService, 'showError')
saveSpy.mockReturnValueOnce(
throwError(() => new Error('Error saving config'))
)
component.saveConfig()
expect(saveSpy).toHaveBeenCalled()
expect(errorSpy).toHaveBeenCalled()
saveSpy.mockReturnValueOnce(
of({ output_type: OutputTypeConfig.PDF_A } as any)
)
component.saveConfig()
expect(component.initialConfig).toEqual({
output_type: OutputTypeConfig.PDF_A,
})
})
it('should support discard changes', () => {
component.initialConfig = { output_type: OutputTypeConfig.PDF_A2 } as any
component.configForm.patchValue({ output_type: OutputTypeConfig.PDF_A })
component.discardChanges()
expect(component.configForm.get('output_type').value).toEqual(
OutputTypeConfig.PDF_A2
)
})
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' })
component.configForm.patchValue({ user_args: '{ "foo": "bar" }' })
expect(component.errors).toEqual({ user_args: null })
})
it('should upload file, show error if necessary', () => {
const uploadSpy = jest.spyOn(configService, 'uploadFile')
const errorSpy = jest.spyOn(toastService, 'showError')
uploadSpy.mockReturnValueOnce(
throwError(() => new Error('Error uploading file'))
)
component.uploadFile(new File([], 'test.png'), 'app_logo')
expect(uploadSpy).toHaveBeenCalled()
expect(errorSpy).toHaveBeenCalled()
uploadSpy.mockReturnValueOnce(
of({ app_logo: 'https://example.com/logo/test.png' } as any)
)
component.uploadFile(new File([], 'test.png'), 'app_logo')
expect(component.initialConfig).toEqual({
app_logo: 'https://example.com/logo/test.png',
})
})
it('should refresh ui settings after save or upload', () => {
const saveSpy = jest.spyOn(configService, 'saveConfig')
const initSpy = jest.spyOn(settingService, 'initializeSettings')
saveSpy.mockReturnValueOnce(
of({ output_type: OutputTypeConfig.PDF_A } as any)
)
component.saveConfig()
expect(initSpy).toHaveBeenCalled()
const uploadSpy = jest.spyOn(configService, 'uploadFile')
uploadSpy.mockReturnValueOnce(
of({ app_logo: 'https://example.com/logo/test.png' } as any)
)
component.uploadFile(new File([], 'test.png'), 'app_logo')
expect(initSpy).toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,189 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { AbstractControl, FormControl, FormGroup } from '@angular/forms'
import {
BehaviorSubject,
Observable,
Subject,
Subscription,
first,
takeUntil,
} from 'rxjs'
import {
PaperlessConfigOptions,
ConfigCategory,
ConfigOption,
ConfigOptionType,
PaperlessConfig,
} from 'src/app/data/paperless-config'
import { ConfigService } from 'src/app/services/config.service'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
import { SettingsService } from 'src/app/services/settings.service'
@Component({
selector: 'pngx-config',
templateUrl: './config.component.html',
styleUrl: './config.component.scss',
})
export class ConfigComponent
extends ComponentWithPermissions
implements OnInit, OnDestroy, DirtyComponent
{
public readonly ConfigOptionType = ConfigOptionType
// generated dynamically
public configForm = new FormGroup({})
public errors = {}
get optionCategories(): string[] {
return Object.values(ConfigCategory)
}
getCategoryOptions(category: string): ConfigOption[] {
return PaperlessConfigOptions.filter((o) => o.category === category)
}
public loading: boolean = false
initialConfig: PaperlessConfig
store: BehaviorSubject<any>
storeSub: Subscription
isDirty$: Observable<boolean>
private unsubscribeNotifier: Subject<any> = new Subject()
constructor(
private configService: ConfigService,
private toastService: ToastService,
private settingsService: SettingsService
) {
super()
this.configForm.addControl('id', new FormControl())
PaperlessConfigOptions.forEach((option) => {
this.configForm.addControl(option.key, new FormControl())
})
}
ngOnInit(): void {
this.loading = true
this.configService
.getConfig()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (config) => {
this.loading = false
this.initialize(config)
},
error: (e) => {
this.loading = false
this.toastService.showError($localize`Error retrieving config`, e)
},
})
// validate JSON inputs
PaperlessConfigOptions.filter(
(o) => o.type === ConfigOptionType.JSON
).forEach((option) => {
this.configForm
.get(option.key)
.addValidators((control: AbstractControl) => {
if (!control.value || control.value.toString().length === 0)
return null
try {
JSON.parse(control.value)
} catch (e) {
return [
{
user_args: e,
},
]
}
return null
})
this.configForm.get(option.key).statusChanges.subscribe((status) => {
this.errors[option.key] =
status === 'INVALID' ? $localize`Invalid JSON` : null
})
this.configForm.get(option.key).updateValueAndValidity()
})
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(true)
this.unsubscribeNotifier.complete()
}
private initialize(config: PaperlessConfig) {
if (!this.store) {
this.store = new BehaviorSubject(config)
this.store
.asObservable()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((state) => {
this.configForm.patchValue(state, { emitEvent: false })
})
this.isDirty$ = dirtyCheck(this.configForm, this.store.asObservable())
}
this.configForm.patchValue(config)
this.initialConfig = config
}
getDocsUrl(key: string) {
return `https://docs.paperless-ngx.com/configuration/#${key}`
}
public saveConfig() {
this.loading = true
this.configService
.saveConfig(this.configForm.value as PaperlessConfig)
.pipe(takeUntil(this.unsubscribeNotifier), first())
.subscribe({
next: (config) => {
this.loading = false
this.initialize(config)
this.store.next(config)
this.settingsService.initializeSettings().subscribe()
this.toastService.showInfo($localize`Configuration updated`)
},
error: (e) => {
this.loading = false
this.toastService.showError(
$localize`An error occurred updating configuration`,
e
)
},
})
}
public discardChanges() {
this.configForm.reset(this.initialConfig)
}
public uploadFile(file: File, key: string) {
this.loading = true
this.configService
.uploadFile(file, this.configForm.value['id'], key)
.pipe(takeUntil(this.unsubscribeNotifier), first())
.subscribe({
next: (config) => {
this.loading = false
this.initialize(config)
this.store.next(config)
this.settingsService.initializeSettings().subscribe()
this.toastService.showInfo($localize`File successfully updated`)
},
error: (e) => {
this.loading = false
this.toastService.showError(
$localize`An error occurred uploading file`,
e
)
},
})
}
}

View File

@@ -1,30 +1,44 @@
<pngx-page-header title="Logs" i18n-title>
<div class="form-check form-switch" (click)="toggleAutoRefresh()">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
<pngx-page-header
title="Logs"
i18n-title
info="Review the log files for the application and for email checking."
i18n-info>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" (click)="toggleAutoRefresh()" [attr.checked]="autoRefreshInterval">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div>
</pngx-page-header>
<ul ngbNav #nav="ngbNav" [(activeId)]="activeLog" (activeIdChange)="reloadLogs()" class="nav-tabs">
<li *ngFor="let logFile of logFiles" [ngbNavItem]="logFile">
<a ngbNavLink>
{{logFile}}.log
</a>
</li>
<div *ngIf="isLoading || !logFiles.length" class="ps-2 d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container *ngIf="!logFiles.length" i18n>Loading...</ng-container>
</div>
@for (logFile of logFiles; track logFile) {
<li [ngbNavItem]="logFile">
<a ngbNavLink>
{{logFile}}.log
</a>
</li>
}
@if (isLoading || !logFiles.length) {
<div class="ps-2 d-flex align-items-center">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
@if (!logFiles.length) {
<ng-container i18n>Loading...</ng-container>
}
</div>
}
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="bg-dark p-3 text-light font-monospace log-container" #logContainer>
<div *ngIf="isLoading && logFiles.length">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</div>
<p
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
*ngFor="let log of logs">{{log}}</p>
@if (isLoading && logFiles.length) {
<div>
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
<ng-container i18n>Loading...</ng-container>
</div>
}
@for (log of logs; track log) {
<p
class="m-0 p-0 log-entry-{{getLogLevel(log)}}"
>{{log}}</p>
}
</div>

View File

@@ -11,6 +11,7 @@ import { of, throwError } from 'rxjs'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { NgbModule, NgbNavLink } from '@ng-bootstrap/ng-bootstrap'
import { BrowserModule, By } from '@angular/platform-browser'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
const paperless_logs = [
'[2023-05-29 03:05:01,224] [DEBUG] [paperless.tasks] Training data unchanged.',
@@ -37,7 +38,12 @@ describe('LogsComponent', () => {
TestBed.configureTestingModule({
declarations: [LogsComponent, PageHeaderComponent],
providers: [],
imports: [HttpClientTestingModule, BrowserModule, NgbModule],
imports: [
HttpClientTestingModule,
BrowserModule,
NgbModule,
NgxBootstrapIconsModule.pick(allIcons),
],
}).compileComponents()
logService = TestBed.inject(LogService)

View File

@@ -2,9 +2,9 @@ import {
Component,
ElementRef,
OnInit,
AfterViewChecked,
ViewChild,
OnDestroy,
ChangeDetectorRef,
} from '@angular/core'
import { Subject, takeUntil } from 'rxjs'
import { LogService } from 'src/app/services/rest/log.service'
@@ -14,8 +14,11 @@ import { LogService } from 'src/app/services/rest/log.service'
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'],
})
export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
constructor(private logService: LogService) {}
export class LogsComponent implements OnInit, OnDestroy {
constructor(
private logService: LogService,
private changedetectorRef: ChangeDetectorRef
) {}
public logs: string[] = []
@@ -47,13 +50,10 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
})
}
ngAfterViewChecked() {
this.scrollToBottom()
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(true)
this.unsubscribeNotifier.complete()
clearInterval(this.autoRefreshInterval)
}
reloadLogs() {
@@ -65,6 +65,7 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
next: (result) => {
this.logs = result
this.isLoading = false
this.scrollToBottom()
},
error: () => {
this.logs = []
@@ -88,6 +89,7 @@ export class LogsComponent implements OnInit, AfterViewChecked, OnDestroy {
}
scrollToBottom(): void {
this.changedetectorRef.detectChanges()
this.logContainer?.nativeElement.scroll({
top: this.logContainer.nativeElement.scrollHeight,
left: 0,

View File

@@ -1,11 +1,36 @@
<pngx-page-header title="Settings" i18n-title>
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()"><ng-container i18n>Start tour</ng-container></button>
<a *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" class="btn btn-sm btn-primary ms-3" href="admin/" target="_blank">
<pngx-page-header
title="Settings"
i18n-title
info="Options to customize appearance, notifications, saved views and more. Settings apply to the <strong>current user only</strong>."
i18n-info
>
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()">
<i-bs class="me-1" name="airplane"></i-bs>&nbsp;<ng-container i18n>Start tour</ng-container>
</button>
@if (permissionsService.isAdmin()) {
<button class="btn btn-sm btn-outline-primary position-relative ms-md-5 me-1" (click)="showSystemStatus()"
[disabled]="!systemStatus">
@if (!systemStatus) {
<div class="spinner-border spinner-border-sm me-1 h-75" role="status"></div>
} @else {
<i-bs class="me-2" name="card-checklist"></i-bs>
@if (systemStatusHasErrors) {
<span class="badge bg-body position-absolute top-0 start-100 translate-middle rounded-pill p-0">
<i-bs name="exclamation-circle-fill" class="text-danger" width="1.75em" height="1.75em"></i-bs>
</span>
} @else {
<span class="badge bg-body position-absolute top-0 start-100 translate-middle rounded-pill p-0">
<i-bs name="check-circle-fill" class="text-primary" width="1.75em" height="1.75em"></i-bs>
</span>
}
}
<ng-container i18n>System Status</ng-container>
</button>
<a class="btn btn-sm btn-primary" href="admin/" target="_blank">
<ng-container i18n>Open Django Admin</ng-container>
<svg class="sidebaricon ms-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#arrow-up-right"/>
</svg>
</a>
&nbsp;<i-bs name="arrow-up-right"></i-bs>
</a>
}
</pngx-page-header>
<form [formGroup]="settingsForm" (ngSubmit)="saveSettings()">
@@ -24,10 +49,16 @@
<div class="col">
<select class="form-select" formControlName="displayLanguage">
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale !== 'en-US'"> - {{lang.englishName}}</span></option>
@for (lang of displayLanguageOptions; track lang) {
<option [ngValue]="lang.code">{{lang.name}}@if (lang.code && currentLocale !== 'en-US') {
<span> - {{lang.englishName}}</span>
}</option>
}
</select>
<small *ngIf="displayLanguageIsDirty" class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
@if (displayLanguageIsDirty) {
<small class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
}
</div>
</div>
@@ -39,7 +70,11 @@
<div class="col">
<select class="form-select" formControlName="dateLocale">
<option *ngFor="let lang of dateLocaleOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code"> - {{today | customDate:'shortDate':null:lang.code}}</span></option>
@for (lang of dateLocaleOptions; track lang) {
<option [ngValue]="lang.code">{{lang.name}}@if (lang.code) {
<span> - {{today | customDate:'shortDate':null:lang.code}}</span>
}</option>
}
</select>
</div>
@@ -125,9 +160,7 @@
</div>
<div class="col-2">
<button class="btn btn-link btn-sm pt-2 ps-0" [disabled]="!this.settingsForm.get('themeColor').value" (click)="clearThemeColor()">
<svg fill="currentColor" class="buttonicon-sm me-1">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg><ng-container i18n>Reset</ng-container>
<i-bs width="1em" height="1em" name="x"></i-bs><ng-container i18n>Reset</ng-container>
</button>
</div>
</div>
@@ -137,16 +170,24 @@
<div class="row mb-3">
<div class="offset-md-3 col">
<p i18n>
Update checking works by pinging the public <a href="https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest" target="_blank" rel="noopener noreferrer">GitHub API</a> for the latest release to determine whether a new version is available.<br/>
Actual updating of the app must still be performed manually.
</p>
Update checking works by pinging the public <a href="https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest" target="_blank" rel="noopener noreferrer">GitHub API</a> for the latest release to determine whether a new version is available.<br/>
Actual updating of the app must still be performed manually.
</p>
<p i18n>
<em>No tracking data is collected by the app in any way.</em>
</p>
<em>No tracking data is collected by the app in any way.</em>
</p>
<pngx-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled"></pngx-input-check>
</div>
</div>
<h4 class="mt-4" i18n>Document editing</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<pngx-input-check i18n-title title="Automatically remove inbox tag(s) on save" formControlName="documentEditingRemoveInboxTags"></pngx-input-check>
</div>
</div>
<h4 class="mt-4" i18n>Bulk editing</h4>
<div class="row mb-3">
@@ -156,6 +197,14 @@
</div>
</div>
<h4 class="mt-4" i18n>Global search</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<pngx-input-check i18n-title title="Search database only (do not include advanced search results)" formControlName="searchDbOnly"></pngx-input-check>
</div>
</div>
<h4 class="mt-4" i18n>Notes</h4>
<div class="row mb-3">
@@ -176,8 +225,8 @@
<div class="row mb-3">
<div class="offset-md-3 col">
<p i18n>
Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI
</p>
Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI
</p>
</div>
</div>
<div class="row mb-3">
@@ -279,40 +328,71 @@
</div>
<h4 i18n>Views</h4>
<div formGroupName="savedViews">
<ul class="list-group" formGroupName="savedViews">
<div *ngFor="let view of savedViews" [formGroupName]="view.id" class="row">
<div class="mb-3 col">
<label class="form-label" for="name_{{view.id}}" i18n>Name</label>
<input type="text" class="form-control" formControlName="name" id="name_{{view.id}}">
</div>
<div class="mb-2 col">
<label class="form-label" for="show_on_dashboard_{{view.id}}" i18n>&nbsp;<span class="visually-hidden">Appears on</span></label>
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
<label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
@for (view of savedViews; track view) {
<li class="list-group-item py-3">
<div [formGroupName]="view.id" class="row">
<div class="row">
<div class="col">
<pngx-input-text title="Name" formControlName="name"></pngx-input-text>
</div>
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input" id="show_in_sidebar_{{view.id}}" formControlName="show_in_sidebar">
<label class="form-check-label" for="show_in_sidebar_{{view.id}}" i18n>Show in sidebar</label>
<div class="col">
<div class="form-check form-switch mt-3">
<input type="checkbox" class="form-check-input" id="show_on_dashboard_{{view.id}}" formControlName="show_on_dashboard">
<label class="form-check-label" for="show_on_dashboard_{{view.id}}" i18n>Show on dashboard</label>
</div>
<div class="form-check form-switch">
<input type="checkbox" class="form-check-input" id="show_in_sidebar_{{view.id}}" formControlName="show_in_sidebar">
<label class="form-check-label" for="show_in_sidebar_{{view.id}}" i18n>Show in sidebar</label>
</div>
</div>
<div class="col-auto">
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
<pngx-confirm-button
label="Delete"
i18n-label
(confirm)="deleteSavedView(view)"
*pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }"
buttonClasses="btn-sm btn-outline-danger form-control"
iconName="trash">
</pngx-confirm-button>
</div>
</div>
<div class="mb-2 col-auto">
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }" i18n>Delete</button>
</div>
<div class="row">
<div class="col">
<pngx-input-number i18n-title title="Documents page size" [showAdd]="false" formControlName="page_size"></pngx-input-number>
</div>
<div class="col">
<label class="form-label" for="display_mode_{{view.id}}" i18n>Display as</label>
<select class="form-select" formControlName="display_mode">
<option [ngValue]="DisplayMode.TABLE" i18n>Table</option>
<option [ngValue]="DisplayMode.SMALL_CARDS" i18n>Small Cards</option>
<option [ngValue]="DisplayMode.LARGE_CARDS" i18n>Large Cards</option>
</select>
</div>
@if (displayFields) {
<pngx-input-drag-drop-select i18n-title title="Show" i18n-emptyText emptyText="Default" [items]="displayFields" formControlName="display_fields"></pngx-input-drag-drop-select>
}
</div>
</div>
</li>
}
<div *ngIf="savedViews && savedViews.length === 0" i18n>No saved views defined.</div>
@if (savedViews && savedViews.length === 0) {
<li class="list-group-item">
<div i18n>No saved views defined.</div>
</li>
}
<div *ngIf="!savedViews">
@if (!savedViews) {
<li class="list-group-item">
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
</div>
</li>
}
</div>
</ul>
</ng-template>
</li>
@@ -320,5 +400,6 @@
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<button type="submit" class="btn btn-primary mb-2" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
<button type="submit" class="btn btn-primary mb-2" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
<button type="button" (click)="reset()" class="btn btn-secondary ms-2 mb-2" [disabled]="(isDirty$ | async) === false" i18n>Cancel</button>
</form>

View File

@@ -9,12 +9,14 @@ import {
NgbModule,
NgbAlertModule,
NgbNavLink,
NgbModal,
NgbModalModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { of, throwError } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { SavedView } from 'src/app/data/saved-view'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
@@ -37,6 +39,17 @@ import { TextComponent } from '../../common/input/text/text.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { SettingsComponent } from './settings.component'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
import { SystemStatusService } from 'src/app/services/system-status.service'
import {
SystemStatus,
InstallType,
SystemStatusItemStatus,
} from 'src/app/data/system-status'
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
import { DragDropModule } from '@angular/cdk/drag-drop'
const savedViews = [
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
@@ -63,6 +76,8 @@ describe('SettingsComponent', () => {
let userService: UserService
let permissionsService: PermissionsService
let groupService: GroupService
let modalService: NgbModal
let systemStatusService: SystemStatusService
beforeEach(async () => {
TestBed.configureTestingModule({
@@ -82,6 +97,8 @@ describe('SettingsComponent', () => {
PermissionsUserComponent,
PermissionsGroupComponent,
IfOwnerDirective,
ConfirmButtonComponent,
DragDropSelectComponent,
],
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
imports: [
@@ -92,6 +109,9 @@ describe('SettingsComponent', () => {
ReactiveFormsModule,
NgbAlertModule,
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
NgbModalModule,
DragDropModule,
],
}).compileComponents()
@@ -103,6 +123,8 @@ describe('SettingsComponent', () => {
settingsService.currentUser = users[0]
userService = TestBed.inject(UserService)
permissionsService = TestBed.inject(PermissionsService)
modalService = TestBed.inject(NgbModal)
systemStatusService = TestBed.inject(SystemStatusService)
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
jest
.spyOn(permissionsService, 'currentUserHasObjectPermissions')
@@ -138,7 +160,7 @@ describe('SettingsComponent', () => {
of({
all: savedViews.map((v) => v.id),
count: savedViews.length,
results: (savedViews as PaperlessSavedView[]).concat([]),
results: (savedViews as SavedView[]).concat([]),
})
)
}
@@ -226,9 +248,7 @@ describe('SettingsComponent', () => {
savedViewPatchSpy.mockClear()
// succeed saved views
savedViewPatchSpy.mockReturnValueOnce(
of(savedViews as PaperlessSavedView[])
)
savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
component.saveSettings()
expect(toastErrorSpy).not.toHaveBeenCalled()
expect(savedViewPatchSpy).toHaveBeenCalled()
@@ -289,7 +309,7 @@ describe('SettingsComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled()
expect(storeSpy).toHaveBeenCalled()
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
expect(setSpy).toHaveBeenCalledTimes(24)
expect(setSpy).toHaveBeenCalledTimes(26)
// succeed
storeSpy.mockReturnValueOnce(of(true))
@@ -307,10 +327,15 @@ describe('SettingsComponent', () => {
component.store.getValue()['displayLanguage'] = 'en-US'
component.store.getValue()['updateCheckingEnabled'] = false
component.settingsForm.value.displayLanguage = 'en-GB'
component.settingsForm.value.updateCheckingEnabled = true
jest.spyOn(settingsService, 'storeSettings').mockReturnValueOnce(of(true))
jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
component.saveSettings()
expect(toast.actionName).toEqual('Reload now')
component.settingsForm.value.updateCheckingEnabled = true
component.saveSettings()
expect(toast.actionName).toEqual('Reload now')
toast.action()
})
it('should allow setting theme color, visually apply change immediately but not save', () => {
@@ -335,7 +360,7 @@ describe('SettingsComponent', () => {
const toastSpy = jest.spyOn(toastService, 'showInfo')
const deleteSpy = jest.spyOn(savedViewService, 'delete')
deleteSpy.mockReturnValue(of(true))
component.deleteSavedView(savedViews[0] as PaperlessSavedView)
component.deleteSavedView(savedViews[0] as SavedView)
expect(deleteSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
`Saved view "${savedViews[0].name}" deleted.`
@@ -365,4 +390,62 @@ describe('SettingsComponent', () => {
fixture.detectChanges()
expect(toastErrorSpy).toBeCalled()
})
it('should load system status on initialize, show errors if needed', () => {
const status: SystemStatus = {
pngx_version: '2.4.3',
server_os: 'macOS-14.1.1-arm64-arm-64bit',
install_type: InstallType.BareMetal,
storage: { total: 494384795648, available: 13573525504 },
database: {
type: 'sqlite',
url: '/paperless-ngx/data/db.sqlite3',
status: SystemStatusItemStatus.ERROR,
error: null,
migration_status: {
latest_migration: 'socialaccount.0006_alter_socialaccount_extra_data',
unapplied_migrations: [],
},
},
tasks: {
redis_url: 'redis://localhost:6379',
redis_status: SystemStatusItemStatus.ERROR,
redis_error:
'Error 61 connecting to localhost:6379. Connection refused.',
celery_status: SystemStatusItemStatus.ERROR,
index_status: SystemStatusItemStatus.OK,
index_last_modified: new Date().toISOString(),
index_error: null,
classifier_status: SystemStatusItemStatus.OK,
classifier_last_trained: new Date().toISOString(),
classifier_error: null,
},
}
jest.spyOn(systemStatusService, 'get').mockReturnValue(of(status))
jest.spyOn(permissionsService, 'isAdmin').mockReturnValue(true)
completeSetup()
expect(component['systemStatus']).toEqual(status) // private
expect(component.systemStatusHasErrors).toBeTruthy()
// coverage
component['systemStatus'].database.status = SystemStatusItemStatus.OK
component['systemStatus'].tasks.redis_status = SystemStatusItemStatus.OK
component['systemStatus'].tasks.celery_status = SystemStatusItemStatus.OK
expect(component.systemStatusHasErrors).toBeFalsy()
})
it('should open system status dialog', () => {
const modalOpenSpy = jest.spyOn(modalService, 'open')
completeSetup()
component.showSystemStatus()
expect(modalOpenSpy).toHaveBeenCalledWith(SystemStatusDialogComponent, {
size: 'xl',
})
})
it('should support reset', () => {
completeSetup()
component.settingsForm.get('themeColor').setValue('#ff0000')
component.reset()
expect(component.settingsForm.get('themeColor').value).toEqual('')
})
})

View File

@@ -9,7 +9,11 @@ import {
} from '@angular/core'
import { FormGroup, FormControl } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'
import {
NgbModal,
NgbModalRef,
NgbNavChangeEvent,
} from '@ng-bootstrap/ng-bootstrap'
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import {
@@ -21,10 +25,10 @@ import {
takeUntil,
tap,
} from 'rxjs'
import { PaperlessGroup } from 'src/app/data/paperless-group'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { Group } from 'src/app/data/group'
import { SavedView } from 'src/app/data/saved-view'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { User } from 'src/app/data/user'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
PermissionsService,
@@ -40,6 +44,13 @@ import {
} from 'src/app/services/settings.service'
import { ToastService, Toast } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
import { SystemStatusService } from 'src/app/services/system-status.service'
import {
SystemStatusItemStatus,
SystemStatus,
} from 'src/app/data/system-status'
import { DisplayMode } from 'src/app/data/document'
enum SettingsNavIDs {
General = 1,
@@ -48,6 +59,12 @@ enum SettingsNavIDs {
SavedViews = 4,
}
const systemLanguage = { code: '', name: $localize`Use system language` }
const systemDateFormat = {
code: '',
name: $localize`Use date format of display language`,
}
@Component({
selector: 'pngx-settings',
templateUrl: './settings.component.html',
@@ -57,8 +74,8 @@ export class SettingsComponent
extends ComponentWithPermissions
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
{
SettingsNavIDs = SettingsNavIDs
activeNavID: number
DisplayMode = DisplayMode
savedViewGroup = new FormGroup({})
@@ -82,6 +99,8 @@ export class SettingsComponent
defaultPermsViewGroups: new FormControl(null),
defaultPermsEditUsers: new FormControl(null),
defaultPermsEditGroups: new FormControl(null),
documentEditingRemoveInboxTags: new FormControl(null),
searchDbOnly: new FormControl(null),
notificationsConsumerNewDocument: new FormControl(null),
notificationsConsumerSuccess: new FormControl(null),
@@ -92,7 +111,11 @@ export class SettingsComponent
savedViews: this.savedViewGroup,
})
savedViews: PaperlessSavedView[]
savedViews: SavedView[]
SettingsNavIDs = SettingsNavIDs
get displayFields() {
return this.settings.allDisplayFields
}
store: BehaviorSubject<any>
storeSub: Subscription
@@ -101,8 +124,20 @@ export class SettingsComponent
unsubscribeNotifier: Subject<any> = new Subject()
savePending: boolean = false
users: PaperlessUser[]
groups: PaperlessGroup[]
users: User[]
groups: Group[]
public systemStatus: SystemStatus
get systemStatusHasErrors(): boolean {
return (
this.systemStatus.database.status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.redis_status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.celery_status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.index_status === SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.classifier_status === SystemStatusItemStatus.ERROR
)
}
get computedDateLocale(): string {
return (
@@ -124,7 +159,9 @@ export class SettingsComponent
private usersService: UserService,
private groupsService: GroupService,
private router: Router,
public permissionsService: PermissionsService
public permissionsService: PermissionsService,
private modalService: NgbModal,
private systemStatusService: SystemStatusService
) {
super()
this.settings.settingsSaved.subscribe(() => {
@@ -265,6 +302,10 @@ export class SettingsComponent
defaultPermsEditGroups: this.settings.get(
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS
),
documentEditingRemoveInboxTags: this.settings.get(
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
),
searchDbOnly: this.settings.get(SETTINGS_KEYS.SEARCH_DB_ONLY),
savedViews: {},
}
}
@@ -306,6 +347,9 @@ export class SettingsComponent
name: view.name,
show_on_dashboard: view.show_on_dashboard,
show_in_sidebar: view.show_in_sidebar,
page_size: view.page_size,
display_mode: view.display_mode,
display_fields: view.display_fields,
}
this.savedViewGroup.addControl(
view.id.toString(),
@@ -314,6 +358,9 @@ export class SettingsComponent
name: new FormControl(null),
show_on_dashboard: new FormControl(null),
show_in_sidebar: new FormControl(null),
page_size: new FormControl(null),
display_mode: new FormControl(null),
display_fields: new FormControl([]),
})
)
}
@@ -350,6 +397,12 @@ export class SettingsComponent
// prevents loss of unsaved changes
this.settingsForm.patchValue(currentFormValue)
}
if (this.permissionsService.isAdmin()) {
this.systemStatusService.get().subscribe((status) => {
this.systemStatus = status
})
}
}
private emptyGroup(group: FormGroup) {
@@ -357,12 +410,12 @@ export class SettingsComponent
}
ngOnDestroy() {
if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didnt save
if (this.isDirty) this.settings.updateAppearanceSettings() // in case user changed appearance but didn't save
this.storeSub && this.storeSub.unsubscribe()
this.settings.organizingSidebarSavedViews = false
}
deleteSavedView(savedView: PaperlessSavedView) {
deleteSavedView(savedView: SavedView) {
this.savedViewService.delete(savedView).subscribe(() => {
this.savedViewGroup.removeControl(savedView.id.toString())
this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
@@ -478,6 +531,14 @@ export class SettingsComponent
SETTINGS_KEYS.DEFAULT_PERMS_EDIT_GROUPS,
this.settingsForm.value.defaultPermsEditGroups
)
this.settings.set(
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS,
this.settingsForm.value.documentEditingRemoveInboxTags
)
this.settings.set(
SETTINGS_KEYS.SEARCH_DB_ONLY,
this.settingsForm.value.searchDbOnly
)
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
this.settings
.storeSettings()
@@ -486,8 +547,8 @@ export class SettingsComponent
.subscribe({
next: () => {
this.store.next(this.settingsForm.value)
this.documentListViewService.updatePageSize()
this.settings.updateAppearanceSettings()
this.settings.initializeDisplayFields()
let savedToast: Toast = {
content: $localize`Settings were saved successfully.`,
delay: 5000,
@@ -512,15 +573,11 @@ export class SettingsComponent
}
get displayLanguageOptions(): LanguageOption[] {
return [{ code: '', name: $localize`Use system language` }].concat(
this.settings.getLanguageOptions()
)
return [systemLanguage].concat(this.settings.getLanguageOptions())
}
get dateLocaleOptions(): LanguageOption[] {
return [
{ code: '', name: $localize`Use date format of display language` },
].concat(this.settings.getDateLocaleOptions())
return [systemDateFormat].concat(this.settings.getDateLocaleOptions())
}
get today() {
@@ -529,7 +586,7 @@ export class SettingsComponent
saveSettings() {
// only patch views that have actually changed
const changed: PaperlessSavedView[] = []
const changed: SavedView[] = []
Object.values(this.savedViewGroup.controls)
.filter((g: FormGroup) => !g.pristine)
.forEach((group: FormGroup) => {
@@ -552,7 +609,21 @@ export class SettingsComponent
}
}
reset() {
this.settingsForm.patchValue(this.store.getValue())
}
clearThemeColor() {
this.settingsForm.get('themeColor').patchValue('')
}
showSystemStatus() {
const modal: NgbModalRef = this.modalService.open(
SystemStatusDialogComponent,
{
size: 'xl',
}
)
modal.componentInstance.status = this.systemStatus
}
}

View File

@@ -1,26 +1,27 @@
<pngx-page-header title="File Tasks" i18n-title>
<pngx-page-header
title="File Tasks"
i18n-title
info="File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process."
i18n-info
>
<div class="btn-toolbar col col-md-auto align-items-center">
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedTasks.size === 0">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>&nbsp;<ng-container i18n>Clear selection</ng-container>
<i-bs name="x"></i-bs>&nbsp;<ng-container i18n>Clear selection</ng-container>
</button>
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
</svg>&nbsp;<ng-container i18n>{{dismissButtonText}}</ng-container>
<i-bs name="check2-all"></i-bs>&nbsp;{{dismissButtonText}}
</button>
<div class="form-check form-switch mb-0" (click)="toggleAutoRefresh()">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" [attr.checked]="autoRefreshInterval">
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" (click)="toggleAutoRefresh()" [attr.checked]="autoRefreshInterval">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div>
</div>
</pngx-page-header>
<ng-container *ngIf="!tasksService.completedFileTasks && tasksService.loading">
@if (!tasksService.completedFileTasks && tasksService.loading) {
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
</ng-container>
}
<ng-template let-tasks="tasks" #tasksTemplate>
<table class="table table-striped align-middle border shadow-sm">
@@ -28,99 +29,124 @@
<tr>
<th scope="col">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="all-tasks" [disabled]="currentTasks.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
<input type="checkbox" class="form-check-input" id="all-tasks" [disabled]="currentTasks.length === 0" [(ngModel)]="togggleAll" (click)="toggleAll($event); $event.stopPropagation();">
<label class="form-check-label" for="all-tasks"></label>
</div>
</th>
<th scope="col" i18n>Name</th>
<th scope="col" class="d-none d-lg-table-cell" i18n>Created</th>
<th scope="col" class="d-none d-lg-table-cell" *ngIf="activeTab !== 'started' && activeTab !== 'queued'" i18n>Results</th>
@if (activeTab !== 'started' && activeTab !== 'queued') {
<th scope="col" class="d-none d-lg-table-cell" i18n>Results</th>
}
<th scope="col" class="d-table-cell d-lg-none" i18n>Info</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let task of tasks | slice: (page-1) * pageSize : page * pageSize">
<tr (click)="toggleSelected(task, $event); $event.stopPropagation();">
<td>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="task{{task.id}}" [checked]="selectedTasks.has(task.id)" (click)="toggleSelected(task, $event); $event.stopPropagation();">
<label class="form-check-label" for="task{{task.id}}"></label>
</div>
</td>
<td class="overflow-auto name-col">{{ task.task_file_name }}</td>
<td class="d-none d-lg-table-cell">{{ task.date_created | customDate:'short' }}</td>
<td class="d-none d-lg-table-cell" *ngIf="activeTab !== 'started' && activeTab !== 'queued'">
<div *ngIf="task.result?.length > 50" class="result" (click)="expandTask(task); $event.stopPropagation();"
[ngbPopover]="resultPopover" popoverClass="shadow small mobile" triggers="mouseenter:mouseleave" container="body">
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result | slice:0:50 }}&hellip;</span>
</div>
<span *ngIf="task.result?.length <= 50" class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result }}</span>
<ng-template #resultPopover>
<pre class="small mb-0">{{ task.result | slice:0:300 }}<ng-container *ngIf="task.result.length > 300">&hellip;</ng-container></pre>
<ng-container *ngIf="task.result?.length > 300"><br/><em>(<ng-container i18n>click for full output</ng-container>)</em></ng-container>
</ng-template>
</td>
<td class="d-lg-none">
<button class="btn btn-link" (click)="expandTask(task); $event.stopPropagation();">
<svg fill="currentColor" class="" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
</svg>
</button>
</td>
<td scope="row">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check"/>
</svg>&nbsp;<ng-container i18n>Dismiss</ng-container>
@for (task of tasks | slice: (page-1) * pageSize : page * pageSize; track task) {
<tr (click)="toggleSelected(task, $event); $event.stopPropagation();">
<td>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="task{{task.id}}" [checked]="selectedTasks.has(task.id)" (click)="toggleSelected(task, $event); $event.stopPropagation();">
<label class="form-check-label" for="task{{task.id}}"></label>
</div>
</td>
<td class="overflow-auto name-col">{{ task.task_file_name }}</td>
<td class="d-none d-lg-table-cell">{{ task.date_created | customDate:'short' }}</td>
@if (activeTab !== 'started' && activeTab !== 'queued') {
<td class="d-none d-lg-table-cell">
@if (task.result?.length > 50) {
<div class="result" (click)="expandTask(task); $event.stopPropagation();"
[ngbPopover]="resultPopover" popoverClass="shadow small mobile" triggers="mouseenter:mouseleave" container="body">
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result | slice:0:50 }}&hellip;</span>
</div>
}
@if (task.result?.length <= 50) {
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result }}</span>
}
<ng-template #resultPopover>
<pre class="small mb-0">{{ task.result | slice:0:300 }}@if (task.result.length > 300) {
&hellip;
}</pre>
@if (task.result?.length > 300) {
<br/><em>(<ng-container i18n>click for full output</ng-container>)</em>
}
</ng-template>
</td>
}
<td class="d-lg-none">
<button class="btn btn-link" (click)="expandTask(task); $event.stopPropagation();">
<i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs>
</button>
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<button *ngIf="task.related_document" class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
</svg>&nbsp;<ng-container i18n>Open Document</ng-container>
</td>
<td scope="row">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
<i-bs name="check"></i-bs>&nbsp;<ng-container i18n>Dismiss</ng-container>
</button>
</ng-container>
</div>
</td>
</tr>
<tr>
<td class="p-0" [class.border-0]="expandedTask !== task.id" colspan="5">
<pre #collapse="ngbCollapse" [ngbCollapse]="expandedTask !== task.id" class="small mb-0"><div class="small p-1 p-lg-3 ms-lg-3">{{ task.result }}</div></pre>
</td>
</tr>
</ng-container>
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
@if (task.related_document) {
<button class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
<i-bs name="file-text"></i-bs>&nbsp;<ng-container i18n>Open Document</ng-container>
</button>
}
</ng-container>
</div>
</td>
</tr>
<tr>
<td class="p-0" [class.border-0]="expandedTask !== task.id" colspan="5">
<pre #collapse="ngbCollapse" [ngbCollapse]="expandedTask !== task.id" class="small mb-0"><div class="small p-1 p-lg-3 ms-lg-3">{{ task.result }}</div></pre>
</td>
</tr>
}
</tbody>
</table>
<div class="pb-3 d-sm-flex justify-content-between align-items-center">
<div class="pb-2 pb-sm-0" i18n *ngIf="tasks.length > 0">{tasks.length, plural, =1 {One {{this.activeTabLocalized}} task} other {{{tasks.length || 0}} total {{this.activeTabLocalized}} tasks}}</div>
<ngb-pagination *ngIf="tasks.length > pageSize" [(page)]="page" [pageSize]="pageSize" [collectionSize]="tasks.length" maxSize="8" size="sm"></ngb-pagination>
@if (tasks.length > 0) {
<div class="pb-2 pb-sm-0">
<ng-container i18n>{tasks.length, plural, =1 {One {{this.activeTabLocalized}} task} other {{{tasks.length || 0}} total {{this.activeTabLocalized}} tasks}}</ng-container>
@if (selectedTasks.size > 0) {
<ng-container i18n>&nbsp;({{selectedTasks.size}} selected)</ng-container>
}
</div>
}
@if (tasks.length > pageSize) {
<ngb-pagination [(page)]="page" [pageSize]="pageSize" [collectionSize]="tasks.length" maxSize="8" size="sm"></ngb-pagination>
}
</div>
</ng-template>
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" (hidden)="duringTabChange($event)">
<li ngbNavItem="failed">
<a ngbNavLink i18n>Failed<span *ngIf="tasksService.failedFileTasks.length > 0" class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></a>
<a ngbNavLink i18n>Failed@if (tasksService.failedFileTasks.length > 0) {
<span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span>
}</a>
<ng-template ngbNavContent>
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.failedFileTasks}"></ng-container>
</ng-template>
</li>
<li ngbNavItem="completed">
<a ngbNavLink i18n>Complete<span *ngIf="tasksService.completedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.completedFileTasks.length}}</span></a>
<a ngbNavLink i18n>Complete@if (tasksService.completedFileTasks.length > 0) {
<span class="badge bg-secondary ms-2">{{tasksService.completedFileTasks.length}}</span>
}</a>
<ng-template ngbNavContent>
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.completedFileTasks}"></ng-container>
</ng-template>
</li>
<li ngbNavItem="started">
<a ngbNavLink i18n>Started<span *ngIf="tasksService.startedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.startedFileTasks.length}}</span></a>
<a ngbNavLink i18n>Started@if (tasksService.startedFileTasks.length > 0) {
<span class="badge bg-secondary ms-2">{{tasksService.startedFileTasks.length}}</span>
}</a>
<ng-template ngbNavContent>
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.startedFileTasks}"></ng-container>
</ng-template>
</li>
<li ngbNavItem="queued">
<a ngbNavLink i18n>Queued<span *ngIf="tasksService.queuedFileTasks.length > 0" class="badge bg-secondary ms-2">{{tasksService.queuedFileTasks.length}}</span></a>
<a ngbNavLink i18n>Queued@if (tasksService.queuedFileTasks.length > 0) {
<span class="badge bg-secondary ms-2">{{tasksService.queuedFileTasks.length}}</span>
}</a>
<ng-template ngbNavContent>
<ng-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:tasksService.queuedFileTasks}"></ng-container>
</ng-template>

View File

@@ -28,6 +28,8 @@ import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dial
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { TasksComponent } from './tasks.component'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { FormsModule } from '@angular/forms'
const tasks: PaperlessTask[] = [
{
@@ -138,6 +140,8 @@ describe('TasksComponent', () => {
NgbModule,
HttpClientTestingModule,
RouterTestingModule.withRoutes(routes),
NgxBootstrapIconsModule.pick(allIcons),
FormsModule,
],
}).compileComponents()

View File

@@ -18,6 +18,7 @@ export class TasksComponent
{
public activeTab: string
public selectedTasks: Set<number> = new Set()
public togggleAll: boolean = false
public expandedTask: number
public pageSize: number = 25
@@ -46,6 +47,7 @@ export class TasksComponent
ngOnDestroy() {
this.tasksService.cancelPending()
clearInterval(this.autoRefreshInterval)
}
dismissTask(task: PaperlessTask) {
@@ -119,6 +121,7 @@ export class TasksComponent
}
clearSelection() {
this.togggleAll = false
this.selectedTasks.clear()
}

View File

@@ -1,97 +1,94 @@
<pngx-page-header title="Users & Groups" i18n-title>
<pngx-page-header
title="Users & Groups"
i18n-title
info="Create, delete and edit users and groups."
i18n-info
infoLink="usage/#users-and-groups"
>
</pngx-page-header>
<ng-container *ngIf="users">
<h4 class="d-flex">
<ng-container i18n>Users</ng-container>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add User</ng-container>
</button>
</h4>
<ul class="list-group">
<li class="list-group-item">
@if (users) {
<h4 class="d-flex">
<ng-container i18n>Users</ng-container>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editUser()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.User }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add User</ng-container>
</button>
</h4>
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col" i18n>Username</div>
<div class="col" i18n>Name</div>
<div class="col" i18n>Groups</div>
<div class="col" i18n>Actions</div>
</div>
</li>
@for (user of users; track user) {
<li class="list-group-item">
<div class="row">
<div class="col" i18n>Username</div>
<div class="col" i18n>Name</div>
<div class="col" i18n>Groups</div>
<div class="col" i18n>Actions</div>
</div>
</li>
<li *ngFor="let user of users" class="list-group-item">
<div class="row">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editUser(user)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.User)">{{user.username}}</button></div>
<div class="col d-flex align-items-center">{{user.first_name}} {{user.last_name}}</div>
<div class="col d-flex align-items-center">{{user.groups?.map(getGroupName, this).join(', ')}}</div>
<div class="col">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editUser(user)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.User)">{{user.username}}</button></div>
<div class="col d-flex align-items-center">{{user.first_name}} {{user.last_name}}</div>
<div class="col d-flex align-items-center">{{user.groups?.map(getGroupName, this).join(', ')}}</div>
<div class="col">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editUser(user)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.User }">
<svg class="buttonicon-sm" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
</svg>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.User }">
<svg class="buttonicon-sm" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editUser(user)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.User }">
<i-bs width="1em" height="1em" name="pencil"></i-bs>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteUser(user)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.User }">
<i-bs width="1em" height="1em" name="trash"></i-bs>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
</div>
</div>
</li>
</ul>
</ng-container>
</li>
}
</ul>
}
<ng-container *ngIf="groups">
<h4 class="mt-4 d-flex">
<ng-container i18n>Groups</ng-container>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
<svg class="sidebaricon me-1" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>
<ng-container i18n>Add Group</ng-container>
</button>
</h4>
<ul *ngIf="groups.length > 0" class="list-group">
<li class="list-group-item">
@if (groups) {
<h4 class="mt-4 d-flex">
<ng-container i18n>Groups</ng-container>
<button type="button" class="btn btn-sm btn-outline-primary ms-4" (click)="editGroup()" *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Group }">
<i-bs name="plus-circle"></i-bs>&nbsp;<ng-container i18n>Add Group</ng-container>
</button>
</h4>
<ul class="list-group">
<li class="list-group-item">
<div class="row">
<div class="col" i18n>Name</div>
<div class="col"></div>
<div class="col"></div>
<div class="col" i18n>Actions</div>
</div>
</li>
@for (group of groups; track group) {
<li class="list-group-item">
<div class="row">
<div class="col" i18n>Name</div>
<div class="col"></div>
<div class="col"></div>
<div class="col" i18n>Actions</div>
</div>
</li>
<li *ngFor="let group of groups" class="list-group-item">
<div class="row">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
<div class="col"></div>
<div class="col"></div>
<div class="col">
<div class="col d-flex align-items-center"><button class="btn btn-link p-0 text-start" type="button" (click)="editGroup(group)" [disabled]="!permissionsService.currentUserCan(PermissionAction.Change, PermissionType.Group)">{{group.name}}</button></div>
<div class="col"></div>
<div class="col"></div>
<div class="col">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Group }">
<svg class="buttonicon-sm" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
</svg>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Group }">
<svg class="buttonicon-sm" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="editGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.Group }">
<i-bs width="1em" height="1em" name="pencil"></i-bs>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" type="button" (click)="deleteGroup(group)" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Group }">
<i-bs width="1em" height="1em" name="trash"></i-bs>&nbsp;<ng-container i18n>Delete</ng-container>
</button>
</div>
</div>
</div>
</li>
<li *ngIf="groups.length === 0" class="list-group-item" i18n>No groups defined</li>
</ul>
</li>
}
@if (groups.length === 0) {
<li class="list-group-item" i18n>No groups defined</li>
}
</ul>
}
</ng-container>
<div *ngIf="!users || !groups">
@if (!users || !groups) {
<div>
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
<div class="visually-hidden" i18n>Loading...</div>
</div>
</div>
}

View File

@@ -41,8 +41,9 @@ import { TextComponent } from '../../common/input/text/text.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { SettingsComponent } from '../settings/settings.component'
import { UsersAndGroupsComponent } from './users-groups.component'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { PaperlessGroup } from 'src/app/data/paperless-group'
import { User } from 'src/app/data/user'
import { Group } from 'src/app/data/group'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
const users = [
{ id: 1, username: 'user1', is_superuser: false },
@@ -92,6 +93,7 @@ describe('UsersAndGroupsComponent', () => {
ReactiveFormsModule,
NgbAlertModule,
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
],
}).compileComponents()
fixture = TestBed.createComponent(UsersAndGroupsComponent)
@@ -119,7 +121,7 @@ describe('UsersAndGroupsComponent', () => {
of({
all: users.map((a) => a.id),
count: users.length,
results: (users as PaperlessUser[]).concat([]),
results: (users as User[]).concat([]),
})
)
}
@@ -128,7 +130,7 @@ describe('UsersAndGroupsComponent', () => {
of({
all: groups.map((r) => r.id),
count: groups.length,
results: (groups as PaperlessGroup[]).concat([]),
results: (groups as Group[]).concat([]),
})
)
}

View File

@@ -1,8 +1,8 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject, first, takeUntil } from 'rxjs'
import { PaperlessGroup } from 'src/app/data/paperless-group'
import { PaperlessUser } from 'src/app/data/paperless-user'
import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user'
import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service'
import { UserService } from 'src/app/services/rest/user.service'
@@ -23,8 +23,8 @@ export class UsersAndGroupsComponent
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
users: PaperlessUser[]
groups: PaperlessGroup[]
users: User[]
groups: Group[]
unsubscribeNotifier: Subject<any> = new Subject()
@@ -69,7 +69,7 @@ export class UsersAndGroupsComponent
this.unsubscribeNotifier.next(true)
}
editUser(user: PaperlessUser = null) {
editUser(user: User = null) {
var modal = this.modalService.open(UserEditDialogComponent, {
backdrop: 'static',
size: 'xl',
@@ -80,7 +80,7 @@ export class UsersAndGroupsComponent
modal.componentInstance.object = user
modal.componentInstance.succeeded
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((newUser: PaperlessUser) => {
.subscribe((newUser: User) => {
if (
newUser.id === this.settings.currentUser.id &&
(modal.componentInstance as UserEditDialogComponent).passwordIsSet
@@ -107,7 +107,7 @@ export class UsersAndGroupsComponent
})
}
deleteUser(user: PaperlessUser) {
deleteUser(user: User) {
let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})
@@ -133,7 +133,7 @@ export class UsersAndGroupsComponent
})
}
editGroup(group: PaperlessGroup = null) {
editGroup(group: Group = null) {
var modal = this.modalService.open(GroupEditDialogComponent, {
backdrop: 'static',
size: 'lg',
@@ -157,7 +157,7 @@ export class UsersAndGroupsComponent
})
}
deleteGroup(group: PaperlessGroup) {
deleteGroup(group: Group) {
let modal = this.modalService.open(ConfirmDialogComponent, {
backdrop: 'static',
})

View File

@@ -4,25 +4,30 @@
(click)="isMenuCollapsed = !isMenuCollapsed">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand col-auto col-md-3 col-lg-2 me-0 px-3 py-3 order-sm-0" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-auto col-md-3 col-lg-2'" routerLink="/dashboard" tourAnchor="tour.intro">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="me-2" fill="currentColor">
<path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/>
<a class="navbar-brand d-flex align-items-center me-0 px-3 py-3 order-sm-0"
[ngClass]="{ 'slim': slimSidebarEnabled, 'col-auto col-md-3 col-lg-2 col-xxxl-1' : !slimSidebarEnabled, 'py-3' : !customAppTitle?.length || slimSidebarEnabled, 'py-2': customAppTitle?.length }"
routerLink="/dashboard"
tourAnchor="tour.intro">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" height="1.5em" fill="currentColor">
<path
d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z"
transform="translate(0 0)" />
</svg>
<span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span>
<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">
<span class="title">{{customAppTitle}}</span>
<span class="byline text-uppercase font-monospace" i18n>by Paperless-ngx</span>
</div>
} @else {
Paperless-ngx
}
</div>
</a>
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
<svg width="1em" height="1em" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#search"/>
</svg>
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (keyup)="searchFieldKeyup($event)" (selectItem)="itemSelected($event)" i18n-placeholder>
<button type="button" *ngIf="!searchFieldEmpty" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0" (click)="resetSearchField()">
<svg fill="currentColor" class="buttonicon-sm me-1">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>
</button>
</form>
<div class="search-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1">
<div class="col-12 col-md-7">
<pngx-global-search></pngx-global-search>
</div>
</div>
<ul ngbNav class="order-sm-3">
<li ngbDropdown class="nav-item dropdown">
@@ -30,9 +35,7 @@
<span class="small me-2 d-none d-sm-inline">
{{this.settingsService.displayName}}
</span>
<svg width="1.3em" height="1.3em" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person-circle"/>
</svg>
<i-bs width="1.3em" height="1.3em" name="person-circle"></i-bs>
</button>
<div ngbDropdownMenu class="dropdown-menu-end shadow me-2" aria-labelledby="userDropdown">
<div class="d-sm-none">
@@ -40,25 +43,19 @@
<div class="dropdown-divider"></div>
</div>
<button ngbDropdownItem class="nav-link" (click)="editProfile()">
<svg class="sidebaricon me-2" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person"/>
</svg><ng-container i18n>My Profile</ng-container>
<i-bs class="me-2" name="person"></i-bs>&nbsp;<ng-container i18n>My Profile</ng-container>
</button>
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }">
<svg class="sidebaricon me-2" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
</svg><ng-container i18n>Settings</ng-container>
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()"
*pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }">
<i-bs class="me-2" name="gear"></i-bs><ng-container i18n>Settings</ng-container>
</a>
<a ngbDropdownItem class="nav-link" href="accounts/logout/" (click)="onLogout()">
<svg class="sidebaricon me-2" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#door-open"/>
</svg><ng-container i18n>Logout</ng-container>
<a ngbDropdownItem class="nav-link d-flex" href="accounts/logout/" (click)="onLogout()">
<i-bs class="me-2" name="door-open"></i-bs><ng-container i18n>Logout</ng-container>
</a>
<div class="dropdown-divider"></div>
<a ngbDropdownItem class="nav-link" target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com">
<svg class="sidebaricon me-2" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
</svg><ng-container i18n>Documentation</ng-container>
<div class="dropdown-divider"></div>
<a ngbDropdownItem class="nav-link" target="_blank" rel="noopener noreferrer"
href="https://docs.paperless-ngx.com">
<i-bs class="me-2" name="question-circle"></i-bs><ng-container i18n>Documentation</ng-container>
</a>
</div>
</li>
@@ -67,229 +64,277 @@
<div class="container-fluid">
<div class="row">
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse" [ngClass]="slimSidebarEnabled ? 'slim' : 'col-md-3 col-lg-2 col-xxxl-1'" [class.animating]="slimSidebarAnimating" [ngbCollapse]="isMenuCollapsed">
<nav id="sidebarMenu" class="d-md-block bg-light sidebar collapse"
[ngClass]="slimSidebarEnabled ? 'slim' : 'col-md-3 col-lg-2 col-xxxl-1'" [class.animating]="slimSidebarAnimating"
[ngbCollapse]="isMenuCollapsed">
<button class="btn btn-sm btn-dark sidebar-slim-toggler" (click)="toggleSlimSidebar()">
<svg class="sidebaricon-sm" fill="currentColor">
<use *ngIf="slimSidebarEnabled" xlink:href="assets/bootstrap-icons.svg#chevron-double-right"/>
<use *ngIf="!slimSidebarEnabled" xlink:href="assets/bootstrap-icons.svg#chevron-double-left"/>
</svg>
@if (slimSidebarEnabled) {
<i-bs width="0.9em" height="0.9em" name="chevron-double-right"></i-bs>
} @else {
<i-bs width="0.9em" height="0.9em" name="chevron-double-left"></i-bs>
}
</button>
<div class="sidebar-sticky pt-3 d-flex flex-column justify-space-around">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Dashboard" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#house"/>
</svg><span>&nbsp;<ng-container i18n>Dashboard</ng-container></span>
<li class="nav-item app-link">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Dashboard" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="house"></i-bs><span>&nbsp;<ng-container i18n>Dashboard</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#files"/>
</svg><span>&nbsp;<ng-container i18n>Documents</ng-container></span>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="files"></i-bs><span>&nbsp;<ng-container i18n>Documents</ng-container></span>
</a>
</li>
</ul>
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted" *ngIf='savedViewService.loading || savedViewService.sidebarViews?.length > 0'>
<span i18n>Saved views</span>
<div *ngIf="savedViewService.loading" class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
</h6>
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
<li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews"
cdkDrag
[cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
cdkDragPreviewContainer="parent"
cdkDragPreviewClass="navItemDrag"
(cdkDragStarted)="onDragStart($event)"
(cdkDragEnded)="onDragEnd($event)">
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
</svg><span>&nbsp;{{view.name}}</span>
</a>
<div *ngIf="settingsService.organizingSidebarSavedViews" class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
<svg class="sidebaricon text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
</svg>
</div>
</li>
</ul>
</ng-container>
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
<span i18n>Open documents</span>
<div class="nav-group mt-3 mb-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
@if (savedViewService.loading || savedViewService.sidebarViews?.length > 0) {
<h6 class="sidebar-heading px-3 text-muted">
<span i18n>Saved views</span>
@if (savedViewService.loading) {
<div class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
}
</h6>
}
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
@for (view of savedViewService.sidebarViews; track view.id) {
<li class="nav-item w-100 app-link" cdkDrag [cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
cdkDragPreviewContainer="parent" cdkDragPreviewClass="navItemDrag" (cdkDragStarted)="onDragStart($event)"
(cdkDragEnded)="onDragEnd($event)">
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}"
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
popoverClass="popover-slim">
<i-bs class="me-1" name="funnel"></i-bs><span>&nbsp;{{view.name}}</span>
</a>
@if (settingsService.organizingSidebarSavedViews) {
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
<i-bs name="grip-vertical"></i-bs>
</div>
}
</li>
}
</ul>
</div>
<div class="nav-group mt-3 mb-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
@if (openDocuments.length > 0) {
<h6 class="sidebar-heading px-3 text-muted">
<span i18n>Open documents</span>
</h6>
}
<ul class="nav flex-column mb-2">
@for (d of openDocuments; track d) {
<li class="nav-item w-100 app-link">
<a class="nav-link app-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}"
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle"
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
popoverClass="popover-slim">
<i-bs class="me-1" name="file-text"></i-bs><span>&nbsp;{{d.title | documentTitle}}</span>
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
<i-bs name="x"></i-bs>
</span>
</a>
</li>
}
@if (openDocuments.length >= 1) {
<li class="nav-item w-100 app-link">
<a class="nav-link app-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()"
ngbPopover="Close all" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="x"></i-bs><span>&nbsp;<ng-container i18n>Close all</ng-container></span>
</a>
</li>
}
</ul>
</div>
<div class="nav-group mt-3 mb-1">
<h6 class="sidebar-heading px-3 text-muted">
<span i18n>Manage</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item w-100" *ngFor='let d of openDocuments'>
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
</svg><span>&nbsp;{{d.title | documentTitle}}</span>
<span class="close" (click)="closeDocument(d); $event.preventDefault()">
<svg fill="currentColor" class="toolbaricon">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>
</span>
<li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="person"></i-bs><span>&nbsp;<ng-container i18n>Correspondents</ng-container></span>
</a>
</li>
<li class="nav-item w-100" *ngIf="openDocuments.length >= 1">
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg><span>&nbsp;<ng-container i18n>Close all</ng-container></span>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }"
tourAnchor="tour.tags">
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags"
i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body"
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="tags"></i-bs><span>&nbsp;<ng-container i18n>Tags</ng-container></span>
</a>
</li>
<li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Document Types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="hash"></i-bs><span>&nbsp;<ng-container i18n>Document Types</ng-container></span>
</a>
</li>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Storage Paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="folder"></i-bs><span>&nbsp;<ng-container i18n>Storage Paths</ng-container></span>
</a>
</li>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
<a class="nav-link" routerLink="customfields" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Custom Fields" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="ui-radios"></i-bs><span>&nbsp;<ng-container i18n>Custom Fields</ng-container></span>
</a>
</li>
<li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Workflow }"
tourAnchor="tour.workflows">
<a class="nav-link" routerLink="workflows" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Workflows" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="boxes"></i-bs><span>&nbsp;<ng-container i18n>Workflows</ng-container></span>
</a>
</li>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }"
tourAnchor="tour.mail">
<a class="nav-link" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail"
i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body"
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="envelope"></i-bs><span>&nbsp;<ng-container i18n>Mail</ng-container></span>
</a>
</li>
</ul>
</ng-container>
</div>
<h6 class="sidebar-heading px-3 mt-3 mb-1 text-muted">
<span i18n>Manage</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person"/>
</svg><span>&nbsp;<ng-container i18n>Correspondents</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }" tourAnchor="tour.tags">
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
</svg><span>&nbsp;<ng-container i18n>Tags</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document Types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
</svg><span>&nbsp;<ng-container i18n>Document Types</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage Paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
</svg><span>&nbsp;<ng-container i18n>Storage Paths</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.CustomField }">
<a class="nav-link" routerLink="customfields" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Custom Fields" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#ui-radios"/>
</svg><span>&nbsp;<ng-container i18n>Custom Fields</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.ConsumptionTemplate }" tourAnchor="tour.consumption-templates">
<a class="nav-link" routerLink="templates" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Consumption templates" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-earmark-ruled"/>
</svg><span>&nbsp;<ng-container i18n>Templates</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.MailAccount }" tourAnchor="tour.mail">
<a class="nav-link" routerLink="mail" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Mail" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#envelope"/>
</svg><span>&nbsp;<ng-container i18n>Mail</ng-container></span>
</a>
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-auto pt-4 mb-1 text-muted">
<span i18n>Administration</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
</svg><span>&nbsp;<ng-container i18n>Settings</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
<a class="nav-link" routerLink="usersgroups" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Users & Groups" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#people"/>
</svg><span>&nbsp;<ng-container i18n>Users & Groups</ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#list-task"/>
</svg><span>&nbsp;<ng-container i18n>File Tasks<span *ngIf="tasksService.failedFileTasks.length > 0"><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span></ng-container></span>
</a>
</li>
<li class="nav-item" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }">
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
</svg><span>&nbsp;<ng-container i18n>Logs</ng-container></span>
</a>
</li>
<li class="nav-item mt-2" tourAnchor="tour.outro">
<a class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com" ngbPopover="Documentation" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#question-circle"/>
</svg><span class="ms-1">&nbsp;<ng-container i18n>Documentation</ng-container></span>
</a>
</li>
<li class="nav-item" [class.visually-hidden]="slimSidebarEnabled">
<div class="px-3 py-0 text-muted small d-flex align-items-center flex-wrap">
<div class="me-3">
<a class="text-muted text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx" ngbPopover="GitHub" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
{{ versionString }}
<div class="nav-group mt-auto mb-1">
<h6 class="sidebar-heading px-3 pt-4 text-muted">
<span i18n>Administration</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }"
tourAnchor="tour.settings">
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="gear"></i-bs><span>&nbsp;<ng-container i18n>Settings</ng-container></span>
</a>
</li>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.AppConfig }">
<a class="nav-link" routerLink="config" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Configuration" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="sliders2-vertical"></i-bs><span>&nbsp;<ng-container i18n>Configuration</ng-container></span>
</a>
</li>
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.User }">
<a class="nav-link" routerLink="usersgroups" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Users & Groups" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="people"></i-bs><span>&nbsp;<ng-container i18n>Users & Groups</ng-container></span>
</a>
</li>
<li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }"
tourAnchor="tour.file-tasks">
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="list-task"></i-bs><span>&nbsp;<ng-container i18n>File Tasks</ng-container>@if (tasksService.failedFileTasks.length > 0) {
<span><span class="badge bg-danger ms-2 d-inline">{{tasksService.failedFileTasks.length}}</span></span>
}</span>
@if (tasksService.failedFileTasks.length > 0 && slimSidebarEnabled) {
<span class="badge bg-danger position-absolute top-0 end-0 d-none d-md-block">{{tasksService.failedFileTasks.length}}</span>
}
</a>
</li>
@if (permissionsService.isAdmin()) {
<li class="nav-item app-link">
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs"
i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body"
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="text-left"></i-bs><span>&nbsp;<ng-container i18n>Logs</ng-container></span>
</a>
</div>
<div *ngIf="!settingsService.updateCheckingIsSet || appRemoteVersion" class="version-check">
<ng-template #updateAvailablePopContent>
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span>
</ng-template>
<ng-template #updateCheckingNotEnabledPopContent>
<p class="small mb-2">
<ng-container i18n>Paperless-ngx can automatically check for updates</ng-container>
</p>
<div class="btn-group btn-group-xs flex-fill w-100">
<button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button>
<button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button>
</li>
}
<li class="nav-item mt-2" tourAnchor="tour.outro">
<a class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap text-decoration-none"
target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com" ngbPopover="Documentation"
i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body"
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="d-flex" name="question-circle"></i-bs><span class="ms-1">&nbsp;<ng-container i18n>Documentation</ng-container></span>
</a>
</li>
<li class="nav-item" [class.visually-hidden]="slimSidebarEnabled">
<div class="px-3 py-0 text-muted small d-flex align-items-center flex-wrap">
<div class="me-3">
<a class="text-muted text-decoration-none" target="_blank" rel="noopener noreferrer"
href="https://github.com/paperless-ngx/paperless-ngx" ngbPopover="GitHub" i18n-ngbPopover
[disablePopover]="!slimSidebarEnabled" placement="end" container="body"
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
{{ versionString }}
</a>
</div>
@if (!settingsService.updateCheckingIsSet || appRemoteVersion) {
<div class="version-check">
<ng-template #updateAvailablePopContent>
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is
available.</ng-container><br /><ng-container i18n>Click to view.</ng-container></span>
</ng-template>
<ng-template #updateCheckingNotEnabledPopContent>
<p class="small mb-2">
<ng-container i18n>Paperless-ngx can automatically check for updates</ng-container>
</p>
<div class="btn-group btn-group-xs flex-fill w-100">
<button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button>
<button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button>
</div>
<p class="small mb-0 mt-2">
<a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n>
How does this work?
</a>
</p>
</ng-template>
@if (settingsService.updateCheckingIsSet) {
@if (appRemoteVersion.update_available) {
<a class="small text-decoration-none" target="_blank" rel="noopener noreferrer"
href="https://github.com/paperless-ngx/paperless-ngx/releases"
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave"
container="body">
<i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs>
@if (appRemoteVersion?.update_available) {
&nbsp;<ng-container i18n>Update available</ng-container>
}
</a>
}
} @else {
<a class="small text-decoration-none" routerLink="/settings" fragment="update-checking"
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter"
container="body">
<i-bs width="1.2em" height="1.2em" name="info-circle"></i-bs>
</a>
}
</div>
<p class="small mb-0 mt-2">
<a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n>
How does this work?
</a>
</p>
</ng-template>
<ng-container *ngIf="settingsService.updateCheckingIsSet; else updateCheckNotSet">
<a *ngIf="appRemoteVersion.update_available" class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases"
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
</svg>
<ng-container *ngIf="appRemoteVersion?.update_available" i18n>Update available</ng-container>
</a>
</ng-container>
<ng-template #updateCheckNotSet>
<a class="small text-decoration-none" routerLink="/settings" fragment="update-checking"
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter" container="body">
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
</svg>
</a>
</ng-template>
}
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main role="main" class="ms-sm-auto px-md-4" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'col-md-9 col-lg-10 col-xxxl-11'">
<main role="main" class="ms-sm-auto px-md-4"
[ngClass]="slimSidebarEnabled ? 'col-slim' : 'col-md-9 col-lg-10 col-xxxl-11'">
<router-outlet></router-outlet>
</main>
</div>

View File

@@ -18,6 +18,10 @@
height: 0.8em;
}
.nav-group:not(:has(.app-link)) .sidebar-heading {
display: none !important;
}
// These come from the col-* classes for non-slim sidebar, needed for animation
@media (min-width: 768px) {
max-width: 25%;
@@ -152,9 +156,9 @@ main {
font-weight: bold;
}
.sidebaricon {
margin-right: 4px;
color: inherit;
i-bs {
position: relative;
top: -1px;
}
}
@@ -186,11 +190,11 @@ main {
width: 1.8rem;
height: 100%;
svg {
i-bs {
opacity: 0.5;
}
&:hover svg {
&:hover i-bs {
opacity: 1;
}
}
@@ -205,7 +209,7 @@ main {
text-decoration: underline;
}
svg {
i-bs {
margin-bottom: 2px;
}
}
@@ -217,9 +221,16 @@ main {
*/
.navbar-brand {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
font-size: 1rem;
.flex-column {
padding: 0.15rem 0;
}
.byline {
font-size: 0.5rem;
letter-spacing: 0.1rem;
}
}
@media screen and (min-width: 768px) {
@@ -241,58 +252,11 @@ main {
.navbar .dropdown-menu {
font-size: 0.875rem; // body size
a svg {
a i-bs {
opacity: 0.6;
}
}
.navbar .search-form-container {
max-width: 550px;
form {
position: relative;
> svg {
position: absolute;
left: 0.6rem;
top: 0.5rem;
color: rgba(255, 255, 255, 0.6);
}
}
&:focus-within {
form > svg {
display: none;
}
.form-control::placeholder {
color: rgba(255, 255, 255, 0);
}
}
.form-control {
color: rgba(255, 255, 255, 0.3);
background-color: rgba(0, 0, 0, 0.15);
padding-left: 1.8rem;
border-color: rgba(255, 255, 255, 0.2);
transition: all .3s ease, padding-left 0s ease, background-color 0s ease; // Safari requires all
max-width: 600px;
min-width: 300px; // 1/2 max
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
&:focus {
background-color: rgba(0, 0, 0, 0.3);
color: var(--bs-light);
flex-grow: 1;
padding-left: 0.5rem;
}
}
}
.version-check {
animation: pulse 2s ease-in-out 0s 1;
}
@@ -312,10 +276,10 @@ main {
}
}
.nav-item .position-absolute {
.nav-item > .position-absolute {
cursor: move;
}
::ng-deep .navItemDrag .position-absolute svg {
::ng-deep .navItemDrag .position-absolute i-bs {
display: none;
}

View File

@@ -15,24 +15,28 @@ import { RouterTestingModule } from '@angular/router/testing'
import { SettingsService } from 'src/app/services/settings.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { RemoteVersionService } from 'src/app/services/rest/remote-version.service'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { of, throwError } from 'rxjs'
import { ToastService } from 'src/app/services/toast.service'
import {
DjangoMessageLevel,
DjangoMessagesService,
} from 'src/app/services/django-messages.service'
import { environment } from 'src/environments/environment'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { ActivatedRoute, Router } from '@angular/router'
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import { SearchService } from 'src/app/services/rest/search.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'
import { routes } from 'src/app/app-routing.module'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SavedView } from 'src/app/data/saved-view'
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { GlobalSearchComponent } from './global-search/global-search.component'
const saved_views = [
{
@@ -82,16 +86,19 @@ describe('AppFrameComponent', () => {
let permissionsService: PermissionsService
let remoteVersionService: RemoteVersionService
let toastService: ToastService
let messagesService: DjangoMessagesService
let openDocumentsService: OpenDocumentsService
let searchService: SearchService
let documentListViewService: DocumentListViewService
let router: Router
let savedViewSpy
let modalService: NgbModal
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [AppFrameComponent, IfPermissionsDirective],
declarations: [
AppFrameComponent,
IfPermissionsDirective,
GlobalSearchComponent,
],
imports: [
HttpClientTestingModule,
BrowserModule,
@@ -101,6 +108,7 @@ describe('AppFrameComponent', () => {
ReactiveFormsModule,
DragDropModule,
NgbModalModule,
NgxBootstrapIconsModule.pick(allIcons),
],
providers: [
SettingsService,
@@ -121,6 +129,7 @@ describe('AppFrameComponent', () => {
RemoteVersionService,
IfPermissionsDirective,
ToastService,
DjangoMessagesService,
OpenDocumentsService,
SearchService,
NgbModal,
@@ -149,9 +158,8 @@ describe('AppFrameComponent', () => {
permissionsService = TestBed.inject(PermissionsService)
remoteVersionService = TestBed.inject(RemoteVersionService)
toastService = TestBed.inject(ToastService)
messagesService = TestBed.inject(DjangoMessagesService)
openDocumentsService = TestBed.inject(OpenDocumentsService)
searchService = TestBed.inject(SearchService)
documentListViewService = TestBed.inject(DocumentListViewService)
modalService = TestBed.inject(NgbModal)
router = TestBed.inject(Router)
@@ -248,7 +256,7 @@ describe('AppFrameComponent', () => {
expect(toastSpy).toHaveBeenCalled()
})
it('should support collapsable menu', () => {
it('should support collapsible menu', () => {
const button: HTMLButtonElement = (
fixture.nativeElement as HTMLDivElement
).querySelector('button[data-toggle=collapse]')
@@ -287,47 +295,6 @@ describe('AppFrameComponent', () => {
expect(component.canDeactivate()).toBeFalsy()
})
it('should call autocomplete endpoint on input', fakeAsync(() => {
const autocompleteSpy = jest.spyOn(searchService, 'autocomplete')
component.searchAutoComplete(of('hello')).subscribe()
tick(250)
expect(autocompleteSpy).toHaveBeenCalled()
component.searchAutoComplete(of('hello world 1')).subscribe()
tick(250)
expect(autocompleteSpy).toHaveBeenCalled()
}))
it('should support reset search field', () => {
const resetSpy = jest.spyOn(component, 'resetSearchField')
const input = (fixture.nativeElement as HTMLDivElement).querySelector(
'input'
) as HTMLInputElement
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }))
expect(resetSpy).toHaveBeenCalled()
})
it('should support choosing a search item', () => {
expect(component.searchField.value).toEqual('')
component.itemSelected({ item: 'hello', preventDefault: () => true })
expect(component.searchField.value).toEqual('hello ')
component.itemSelected({ item: 'world', preventDefault: () => true })
expect(component.searchField.value).toEqual('hello world ')
})
it('should navigate via quickFilter on search', () => {
const str = 'hello world '
component.searchField.patchValue(str)
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
component.search()
expect(qfSpy).toHaveBeenCalledWith([
{
rule_type: FILTER_FULLTEXT_QUERY,
value: str.trim(),
},
])
})
it('should disable global dropzone on start drag + drop, re-enable after', () => {
expect(settingsService.globalDropzoneEnabled).toBeTruthy()
component.onDragStart(null)
@@ -341,7 +308,7 @@ describe('AppFrameComponent', () => {
const toastSpy = jest.spyOn(toastService, 'showInfo')
jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
PaperlessSavedView[]
SavedView[]
>)
expect(settingsSpy).toHaveBeenCalledWith([
saved_views[2],
@@ -364,7 +331,7 @@ describe('AppFrameComponent', () => {
.spyOn(settingsService, 'storeSettings')
.mockReturnValue(throwError(() => new Error('unable to save')))
component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
PaperlessSavedView[]
SavedView[]
>)
expect(toastSpy).toHaveBeenCalled()
})
@@ -376,4 +343,19 @@ describe('AppFrameComponent', () => {
backdrop: 'static',
})
})
it('should show toasts for django messages', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
jest.spyOn(messagesService, 'get').mockReturnValue([
{ level: DjangoMessageLevel.WARNING, message: 'Test warning' },
{ level: DjangoMessageLevel.ERROR, message: 'Test error' },
{ level: DjangoMessageLevel.SUCCESS, message: 'Test success' },
{ level: DjangoMessageLevel.INFO, message: 'Test info' },
{ level: DjangoMessageLevel.DEBUG, message: 'Test debug' },
])
component.ngOnInit()
expect(toastErrorSpy).toHaveBeenCalledTimes(2)
expect(toastInfoSpy).toHaveBeenCalledTimes(3)
})
})

View File

@@ -1,22 +1,16 @@
import { Component, HostListener, OnInit } from '@angular/core'
import { FormControl } from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import { from, Observable } from 'rxjs'
import {
debounceTime,
distinctUntilChanged,
map,
switchMap,
first,
} from 'rxjs/operators'
import { PaperlessDocument } from 'src/app/data/paperless-document'
import { Observable } from 'rxjs'
import { first } from 'rxjs/operators'
import { Document } from 'src/app/data/document'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import {
DjangoMessageLevel,
DjangoMessagesService,
} from 'src/app/services/django-messages.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SearchService } from 'src/app/services/rest/search.service'
import { environment } from 'src/environments/environment'
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type'
import {
RemoteVersionService,
AppRemoteVersion,
@@ -24,7 +18,7 @@ import {
import { SettingsService } from 'src/app/services/settings.service'
import { TasksService } from 'src/app/services/tasks.service'
import { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import {
@@ -32,7 +26,7 @@ import {
PermissionsService,
PermissionType,
} from 'src/app/services/permissions.service'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { SavedView } from 'src/app/data/saved-view'
import {
CdkDragStart,
CdkDragEnd,
@@ -41,6 +35,7 @@ import {
} from '@angular/cdk/drag-drop'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
import { ObjectWithId } from 'src/app/data/object-with-id'
@Component({
selector: 'pngx-app-frame',
@@ -58,21 +53,18 @@ export class AppFrameComponent
slimSidebarAnimating: boolean = false
searchField = new FormControl('')
constructor(
public router: Router,
private activatedRoute: ActivatedRoute,
private openDocumentsService: OpenDocumentsService,
private searchService: SearchService,
public savedViewService: SavedViewService,
private remoteVersionService: RemoteVersionService,
private list: DocumentListViewService,
public settingsService: SettingsService,
public tasksService: TasksService,
private readonly toastService: ToastService,
private modalService: NgbModal,
permissionsService: PermissionsService
public permissionsService: PermissionsService,
private djangoMessagesService: DjangoMessagesService
) {
super()
@@ -91,6 +83,20 @@ export class AppFrameComponent
this.checkForUpdates()
}
this.tasksService.reload()
this.djangoMessagesService.get().forEach((message) => {
switch (message.level) {
case DjangoMessageLevel.ERROR:
case DjangoMessageLevel.WARNING:
this.toastService.showError(message.message)
break
case DjangoMessageLevel.SUCCESS:
case DjangoMessageLevel.INFO:
case DjangoMessageLevel.DEBUG:
this.toastService.showInfo(message.message)
break
}
})
}
toggleSlimSidebar(): void {
@@ -101,6 +107,10 @@ export class AppFrameComponent
}, 200) // slightly longer than css animation for slim sidebar
}
get customAppTitle(): string {
return this.settingsService.get(SETTINGS_KEYS.APP_TITLE)
}
get slimSidebarEnabled(): boolean {
return this.settingsService.get(SETTINGS_KEYS.SLIM_SIDEBAR)
}
@@ -131,7 +141,7 @@ export class AppFrameComponent
this.closeMenu()
}
get openDocuments(): PaperlessDocument[] {
get openDocuments(): Document[] {
return this.openDocumentsService.getOpenDocuments()
}
@@ -140,60 +150,7 @@ export class AppFrameComponent
return !this.openDocumentsService.hasDirty()
}
get searchFieldEmpty(): boolean {
return this.searchField.value.trim().length == 0
}
resetSearchField() {
this.searchField.reset('')
}
searchFieldKeyup(event: KeyboardEvent) {
if (event.key == 'Escape') {
this.resetSearchField()
}
}
searchAutoComplete = (text$: Observable<string>) =>
text$.pipe(
debounceTime(200),
distinctUntilChanged(),
map((term) => {
if (term.lastIndexOf(' ') != -1) {
return term.substring(term.lastIndexOf(' ') + 1)
} else {
return term
}
}),
switchMap((term) =>
term.length < 2 ? from([[]]) : this.searchService.autocomplete(term)
)
)
itemSelected(event) {
event.preventDefault()
let currentSearch: string = this.searchField.value
let lastSpaceIndex = currentSearch.lastIndexOf(' ')
if (lastSpaceIndex != -1) {
currentSearch = currentSearch.substring(0, lastSpaceIndex + 1)
currentSearch += event.item + ' '
} else {
currentSearch = event.item + ' '
}
this.searchField.patchValue(currentSearch)
}
search() {
this.closeMenu()
this.list.quickFilter([
{
rule_type: FILTER_FULLTEXT_QUERY,
value: (this.searchField.value as string).trim(),
},
])
}
closeDocument(d: PaperlessDocument) {
closeDocument(d: Document) {
this.openDocumentsService
.closeDocument(d)
.pipe(first())
@@ -243,7 +200,7 @@ export class AppFrameComponent
this.settingsService.globalDropzoneEnabled = true
}
onDrop(event: CdkDragDrop<PaperlessSavedView[]>) {
onDrop(event: CdkDragDrop<SavedView[]>) {
const sidebarViews = this.savedViewService.sidebarViews.concat([])
moveItemInArray(sidebarViews, event.previousIndex, event.currentIndex)

View File

@@ -0,0 +1,166 @@
<div ngbDropdown #resultsDropdown="ngbDropdown" (openChange)="onDropdownOpenChange">
<form class="form-inline position-relative">
<i-bs width="1em" height="1em" name="search"></i-bs>
<div class="input-group">
<div class="form-control form-control-sm">
<input class="bg-transparent border-0 w-100 h-100" #searchInput type="text" name="query"
placeholder="Search" aria-label="Search" i18n-placeholder
autocomplete="off" spellcheck="false"
[(ngModel)]="query" (ngModelChange)="this.queryDebounce.next($event)" (keydown)="searchInputKeyDown($event)">
<div class="position-absolute top-50 end-0 translate-middle">
@if (loading) {
<div class="spinner-border spinner-border-sm text-muted mt-1"></div>
}
</div>
</div>
@if (query && (searchResults?.documents.length === searchService.searchResultObjectLimit || searchService.searchDbOnly)) {
<button class="btn btn-sm btn-outline-secondary" type="button" (click)="runAdvanedSearch()">
<ng-container i18n>Advanced search</ng-container>
<i-bs width="1em" height="1em" name="arrow-right-short"></i-bs>
</button>
}
</div>
</form>
<ng-template #resultItemTemplate let-item="item" let-nameProp="nameProp" let-type="type" let-icon="icon" let-date="date">
<div #resultItem ngbDropdownItem class="py-2 d-flex align-items-center focus-ring border-0 cursor-pointer" tabindex="-1"
(click)="primaryAction(type, item)"
(mouseenter)="onItemHover($event)">
<i-bs width="1.2em" height="1.2em" name="{{icon}}" class="me-2 text-muted"></i-bs>
<div class="text-truncate">
{{item[nameProp]}}
@if (date) {
<small class="small text-muted">{{date | customDate}}</small>
}
</div>
<div class="btn-group ms-auto">
<button #primaryButton type="button" class="btn btn-sm btn-outline-primary d-flex"
(click)="primaryAction(type, item); $event.stopImmediatePropagation()"
(keydown)="onButtonKeyDown($event)"
[disabled]="disablePrimaryButton(type, item)"
(mouseenter)="onButtonHover($event)">
@if (type === DataType.Document) {
<i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Open</ng-container></span>
} @else if (type === DataType.SavedView) {
<i-bs width="1em" height="1em" name="eye"></i-bs>
<span>&nbsp;<ng-container i18n>Open</ng-container></span>
} @else if (type === DataType.Workflow || type === DataType.CustomField || type === DataType.Group || type === DataType.User || type === DataType.MailAccount || type === DataType.MailRule) {
<i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Edit</ng-container></span>
} @else {
<i-bs width="1em" height="1em" name="filter"></i-bs>
<span>&nbsp;<ng-container i18n>Filter documents</ng-container></span>
}
</button>
@if (type !== DataType.SavedView && type !== DataType.Workflow && type !== DataType.CustomField && type !== DataType.Group && type !== DataType.User && type !== DataType.MailAccount && type !== DataType.MailRule) {
<button #secondaryButton type="button" class="btn btn-sm btn-outline-primary d-flex"
(click)="secondaryAction(type, item); $event.stopImmediatePropagation()"
(keydown)="onButtonKeyDown($event)"
[disabled]="disableSecondaryButton(type, item)"
(mouseenter)="onButtonHover($event)">
@if (type === DataType.Document) {
<i-bs width="1em" height="1em" name="download"></i-bs>
<span>&nbsp;<ng-container i18n>Download</ng-container></span>
} @else {
<i-bs width="1em" height="1em" name="pencil"></i-bs>
<span>&nbsp;<ng-container i18n>Edit</ng-container></span>
}
</button>
}
</div>
</div>
</ng-template>
<div ngbDropdownMenu class="w-100 mh-75 overflow-y-scroll shadow-lg">
<div (keydown)="dropdownKeyDown($event)">
@if (searchResults?.total === 0) {
<h6 class="dropdown-header" i18n="@@searchResults.noResults">No results</h6>
} @else {
@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>
}
}
@if (searchResults?.saved_views.length) {
<h6 class="dropdown-header" i18n="@@searchResults.saved_views">Saved Views</h6>
@for (saved_view of searchResults.saved_views; track saved_view.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: saved_view, nameProp: 'name', type: DataType.SavedView, icon: 'funnel'}"></ng-container>
}
}
@if (searchResults?.tags.length) {
<h6 class="dropdown-header" i18n="@@searchResults.tags">Tags</h6>
@for (tag of searchResults.tags; track tag.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: tag, nameProp: 'name', type: DataType.Tag, icon: 'tag'}"></ng-container>
}
}
@if (searchResults?.correspondents.length) {
<h6 class="dropdown-header" i18n="@@searchResults.correspondents">Correspondents</h6>
@for (correspondent of searchResults.correspondents; track correspondent.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: correspondent, nameProp: 'name', type: DataType.Correspondent, icon: 'person'}"></ng-container>
}
}
@if (searchResults?.document_types.length) {
<h6 class="dropdown-header" i18n="@@searchResults.documentTypes">Document types</h6>
@for (documentType of searchResults.document_types; track documentType.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: documentType, nameProp: 'name', type: DataType.DocumentType, icon: 'file-earmark'}"></ng-container>
}
}
@if (searchResults?.storage_paths.length) {
<h6 class="dropdown-header" i18n="@@searchResults.storagePaths">Storage paths</h6>
@for (storagePath of searchResults.storage_paths; track storagePath.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: storagePath, nameProp: 'name', type: DataType.StoragePath, icon: 'folder'}"></ng-container>
}
}
@if (searchResults?.users.length) {
<h6 class="dropdown-header" i18n="@@searchResults.users">Users</h6>
@for (user of searchResults.users; track user.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: user, nameProp: 'username', type: DataType.User, icon: 'person-square'}"></ng-container>
}
}
@if (searchResults?.groups.length) {
<h6 class="dropdown-header" i18n="@@searchResults.groups">Groups</h6>
@for (group of searchResults.groups; track group.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: group, nameProp: 'name', type: DataType.Group, icon: 'people'}"></ng-container>
}
}
@if (searchResults?.custom_fields.length) {
<h6 class="dropdown-header" i18n="@@searchResults.customFields">Custom fields</h6>
@for (customField of searchResults.custom_fields; track customField.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: customField, nameProp: 'name', type: DataType.CustomField, icon: 'ui-radios'}"></ng-container>
}
}
@if (searchResults?.mail_accounts.length) {
<h6 class="dropdown-header" i18n="@@searchResults.mailAccounts">Mail accounts</h6>
@for (mailAccount of searchResults.mail_accounts; track mailAccount.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: mailAccount, nameProp: 'name', type: DataType.MailAccount, icon: 'envelope-at'}"></ng-container>
}
}
@if (searchResults?.mail_rules.length) {
<h6 class="dropdown-header" i18n="@@searchResults.mailRules">Mail rules</h6>
@for (mailRule of searchResults.mail_rules; track mailRule.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: mailRule, nameProp: 'name', type: DataType.MailRule, icon: 'envelope'}"></ng-container>
}
}
@if (searchResults?.workflows.length) {
<h6 class="dropdown-header" i18n="@@searchResults.workflows">Workflows</h6>
@for (workflow of searchResults.workflows; track workflow.id) {
<ng-container *ngTemplateOutlet="resultItemTemplate; context: {item: workflow, nameProp: 'name', type: DataType.Workflow, icon: 'boxes'}"></ng-container>
}
}
}
</div>
</div>
</div>

View File

@@ -0,0 +1,98 @@
form {
position: relative;
> i-bs[name="search"] {
position: absolute;
left: 0.6rem;
top: .35rem;
color: rgba(255, 255, 255, 0.6);
@media screen and (min-width: 768px) {
// adjust for smaller font size on non-mobile
top: 0.25rem;
}
}
&:focus-within {
i-bs[name="search"],
.badge {
display: none !important;
}
.form-control::placeholder {
color: rgba(255, 255, 255, 0);
}
}
.badge {
font-size: 0.8rem;
}
.input-group .btn {
border-color: rgba(255, 255, 255, 0.2);
color: var(--pngx-primary-text-contrast);
}
.form-control {
color: rgba(255, 255, 255, 0.3);
background-color: rgba(0, 0, 0, 0.15);
padding-left: 1.8rem;
border-color: rgba(255, 255, 255, 0.2);
transition: all .3s ease, padding-left 0s ease, background-color 0s ease; // Safari requires all
> input {
outline: none;
color: var(--pngx-primary-text-contrast);
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
}
&:focus-within {
background-color: rgba(0, 0, 0, 0.3);
color: var(--bs-light);
flex-grow: 1;
padding-left: 0.5rem;
}
}
}
* {
--pngx-focus-alpha: 0;
}
.cursor-pointer {
cursor: pointer;
}
.mh-75 {
max-height: 75vh;
}
.dropdown-item {
&:has(button:focus) {
background-color: var(--pngx-bg-darker);
}
& button {
transition: all 0.3s ease, color 0.15s ease;
max-width: 2rem;
overflow: hidden;
}
& button span {
opacity: 0;
transition: inherit;
}
&:hover button,
&:has(button:focus) button {
max-width: 10rem;
}
&:hover button span,
&:has(button:focus) span {
opacity: 1;
}
}

View File

@@ -0,0 +1,460 @@
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { GlobalSearchComponent } from './global-search.component'
import { of } from 'rxjs'
import { SearchService } from 'src/app/services/rest/search.service'
import { Router } from '@angular/router'
import {
NgbDropdownModule,
NgbModal,
NgbModalModule,
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import {
FILTER_FULLTEXT_QUERY,
FILTER_HAS_CORRESPONDENT_ANY,
FILTER_HAS_DOCUMENT_TYPE_ANY,
FILTER_HAS_STORAGE_PATH_ANY,
FILTER_HAS_TAGS_ANY,
} from 'src/app/data/filter-rule-type'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { DocumentService } from 'src/app/services/rest/document.service'
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
import { ElementRef } from '@angular/core'
import { ToastService } from 'src/app/services/toast.service'
import { DataType } from 'src/app/data/datatype'
const searchResults = {
total: 11,
documents: [
{
id: 1,
title: 'Test',
created_at: new Date(),
updated_at: new Date(),
document_type: { id: 1, name: 'Test' },
storage_path: { id: 1, path: 'Test' },
tags: [],
correspondents: [],
custom_fields: [],
},
],
saved_views: [
{
id: 1,
name: 'TestSavedView',
},
],
correspondents: [
{
id: 1,
name: 'TestCorrespondent',
},
],
document_types: [
{
id: 1,
name: 'TestDocumentType',
},
],
storage_paths: [
{
id: 1,
name: 'TestStoragePath',
},
],
tags: [
{
id: 1,
name: 'TestTag',
},
],
users: [
{
id: 1,
username: 'TestUser',
},
],
groups: [
{
id: 1,
name: 'TestGroup',
},
],
mail_accounts: [
{
id: 1,
name: 'TestMailAccount',
},
],
mail_rules: [
{
id: 1,
name: 'TestMailRule',
},
],
custom_fields: [
{
id: 1,
name: 'TestCustomField',
},
],
workflows: [
{
id: 1,
name: 'TestWorkflow',
},
],
}
describe('GlobalSearchComponent', () => {
let component: GlobalSearchComponent
let fixture: ComponentFixture<GlobalSearchComponent>
let searchService: SearchService
let router: Router
let modalService: NgbModal
let documentService: DocumentService
let documentListViewService: DocumentListViewService
let toastService: ToastService
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GlobalSearchComponent],
imports: [
HttpClientTestingModule,
NgbModalModule,
NgbDropdownModule,
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule.pick(allIcons),
],
}).compileComponents()
searchService = TestBed.inject(SearchService)
router = TestBed.inject(Router)
modalService = TestBed.inject(NgbModal)
documentService = TestBed.inject(DocumentService)
documentListViewService = TestBed.inject(DocumentListViewService)
toastService = TestBed.inject(ToastService)
fixture = TestBed.createComponent(GlobalSearchComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should handle keyboard nav', () => {
const focusSpy = jest.spyOn(component.searchInput.nativeElement, 'focus')
document.dispatchEvent(new KeyboardEvent('keydown', { key: '/' }))
expect(focusSpy).toHaveBeenCalled()
component.searchResults = searchResults as any
component.resultsDropdown.open()
fixture.detectChanges()
component['currentItemIndex'] = 0
component['setCurrentItem']()
const firstItemFocusSpy = jest.spyOn(
component.primaryButtons.get(1).nativeElement,
'focus'
)
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(1)
expect(firstItemFocusSpy).toHaveBeenCalled()
const secondaryItemFocusSpy = jest.spyOn(
component.secondaryButtons.get(1).nativeElement,
'focus'
)
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowRight' })
)
expect(secondaryItemFocusSpy).toHaveBeenCalled()
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowLeft' })
)
expect(firstItemFocusSpy).toHaveBeenCalled()
const zeroItemSpy = jest.spyOn(
component.primaryButtons.get(0).nativeElement,
'focus'
)
component.dropdownKeyDown(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
expect(component['currentItemIndex']).toBe(0)
expect(zeroItemSpy).toHaveBeenCalled()
const inputFocusSpy = jest.spyOn(
component.searchInput.nativeElement,
'focus'
)
component.dropdownKeyDown(new KeyboardEvent('keydown', { key: 'ArrowUp' }))
expect(component['currentItemIndex']).toBe(-1)
expect(inputFocusSpy).toHaveBeenCalled()
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
component['currentItemIndex'] = searchResults.total - 1
component['setCurrentItem']()
component.dropdownKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(-1)
// Search input
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowUp' })
)
expect(component['currentItemIndex']).toBe(searchResults.total - 1)
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'ArrowDown' })
)
expect(component['currentItemIndex']).toBe(0)
component.searchResults = { total: 1 } as any
const primaryActionSpy = jest.spyOn(component, 'primaryAction')
component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(primaryActionSpy).toHaveBeenCalled()
component.query = 'test'
const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset')
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'Escape' })
)
expect(resetSpy).toHaveBeenCalled()
component.query = ''
const blurSpy = jest.spyOn(component.searchInput.nativeElement, 'blur')
component.searchInputKeyDown(
new KeyboardEvent('keydown', { key: 'Escape' })
)
expect(blurSpy).toHaveBeenCalled()
component.searchResults = { total: 1 } as any
component.resultsDropdown.close()
const openSpy = jest.spyOn(component.resultsDropdown, 'open')
component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' }))
expect(openSpy).toHaveBeenCalled()
})
it('should search on query debounce', fakeAsync(() => {
const query = 'test'
const searchSpy = jest.spyOn(searchService, 'globalSearch')
searchSpy.mockReturnValue(of({} as any))
const dropdownOpenSpy = jest.spyOn(component.resultsDropdown, 'open')
component.queryDebounce.next(query)
tick(401)
expect(searchSpy).toHaveBeenCalledWith(query)
expect(dropdownOpenSpy).toHaveBeenCalled()
}))
it('should support primary action', () => {
const object = { id: 1 }
const routerSpy = jest.spyOn(router, 'navigate')
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
const modalSpy = jest.spyOn(modalService, 'open')
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
component.primaryAction(DataType.Document, object)
expect(routerSpy).toHaveBeenCalledWith(['/documents', object.id])
component.primaryAction(DataType.SavedView, object)
expect(routerSpy).toHaveBeenCalledWith(['/view', object.id])
component.primaryAction(DataType.Correspondent, object)
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_CORRESPONDENT_ANY, value: object.id.toString() },
])
component.primaryAction(DataType.DocumentType, object)
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_DOCUMENT_TYPE_ANY, value: object.id.toString() },
])
component.primaryAction(DataType.StoragePath, object)
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_STORAGE_PATH_ANY, value: object.id.toString() },
])
component.primaryAction(DataType.Tag, object)
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_HAS_TAGS_ANY, value: object.id.toString() },
])
component.primaryAction(DataType.User, object)
expect(modalSpy).toHaveBeenCalledWith(UserEditDialogComponent, {
size: 'lg',
})
component.primaryAction(DataType.Group, object)
expect(modalSpy).toHaveBeenCalledWith(GroupEditDialogComponent, {
size: 'lg',
})
component.primaryAction(DataType.MailAccount, object)
expect(modalSpy).toHaveBeenCalledWith(MailAccountEditDialogComponent, {
size: 'xl',
})
component.primaryAction(DataType.MailRule, object)
expect(modalSpy).toHaveBeenCalledWith(MailRuleEditDialogComponent, {
size: 'xl',
})
component.primaryAction(DataType.CustomField, object)
expect(modalSpy).toHaveBeenCalledWith(CustomFieldEditDialogComponent, {
size: 'md',
})
component.primaryAction(DataType.Workflow, object)
expect(modalSpy).toHaveBeenCalledWith(WorkflowEditDialogComponent, {
size: 'xl',
})
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
// fail first
editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled()
// succeed
editDialog.succeeded.emit(true)
expect(toastInfoSpy).toHaveBeenCalled()
})
it('should support secondary action', () => {
const doc = searchResults.documents[0]
const openSpy = jest.spyOn(window, 'open')
component.secondaryAction('document', doc)
expect(openSpy).toHaveBeenCalledWith(documentService.getDownloadUrl(doc.id))
const correspondent = searchResults.correspondents[0]
const modalSpy = jest.spyOn(modalService, 'open')
let modal: NgbModalRef
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
component.secondaryAction(DataType.Correspondent, correspondent)
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md',
})
component.secondaryAction(
DataType.DocumentType,
searchResults.document_types[0]
)
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md',
})
component.secondaryAction(
DataType.StoragePath,
searchResults.storage_paths[0]
)
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md',
})
component.secondaryAction(DataType.Tag, searchResults.tags[0])
expect(modalSpy).toHaveBeenCalledWith(CorrespondentEditDialogComponent, {
size: 'md',
})
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
// fail first
editDialog.failed.emit({ error: 'error creating item' })
expect(toastErrorSpy).toHaveBeenCalled()
// succeed
editDialog.succeeded.emit(true)
expect(toastInfoSpy).toHaveBeenCalled()
})
it('should support reset', () => {
const debounce = jest.spyOn(component.queryDebounce, 'next')
const closeSpy = jest.spyOn(component.resultsDropdown, 'close')
component['reset'](true)
expect(debounce).toHaveBeenCalledWith(null)
expect(component.searchResults).toBeNull()
expect(component['currentItemIndex']).toBe(-1)
expect(closeSpy).toHaveBeenCalled()
})
it('should support focus current item', () => {
component.searchResults = searchResults as any
fixture.detectChanges()
const focusSpy = jest.spyOn(
component.primaryButtons.get(0).nativeElement,
'focus'
)
component['currentItemIndex'] = 0
component['setCurrentItem']()
expect(focusSpy).toHaveBeenCalled()
})
it('should reset on dropdown close', () => {
const resetSpy = jest.spyOn(GlobalSearchComponent.prototype as any, 'reset')
component.onDropdownOpenChange(false)
expect(resetSpy).toHaveBeenCalled()
})
it('should focus button on dropdown item hover', () => {
component.searchResults = searchResults as any
fixture.detectChanges()
const item: ElementRef = component.resultItems.first
const focusSpy = jest.spyOn(
component.primaryButtons.first.nativeElement,
'focus'
)
component.onItemHover({ currentTarget: item.nativeElement } as any)
expect(component['currentItemIndex']).toBe(0)
expect(focusSpy).toHaveBeenCalled()
})
it('should focus on button hover', () => {
const event = { currentTarget: { focus: jest.fn() } }
const focusSpy = jest.spyOn(event.currentTarget, 'focus')
component.onButtonHover(event as any)
expect(focusSpy).toHaveBeenCalled()
})
it('should prevent event propagation for keyboard events on buttons that are not arrows', () => {
const event = { stopImmediatePropagation: jest.fn(), key: 'Enter' }
const stopPropagationSpy = jest.spyOn(event, 'stopImmediatePropagation')
component.onButtonKeyDown(event as any)
expect(stopPropagationSpy).toHaveBeenCalled()
})
it('should support explicit advanced search', () => {
const qfSpy = jest.spyOn(documentListViewService, 'quickFilter')
component.query = 'test'
component.runAdvanedSearch()
expect(qfSpy).toHaveBeenCalledWith([
{ rule_type: FILTER_FULLTEXT_QUERY, value: 'test' },
])
})
})

View File

@@ -0,0 +1,375 @@
import {
Component,
ViewChild,
ElementRef,
ViewChildren,
QueryList,
OnInit,
} from '@angular/core'
import { Router } from '@angular/router'
import { NgbDropdown, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { Subject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
import {
FILTER_FULLTEXT_QUERY,
FILTER_HAS_CORRESPONDENT_ANY,
FILTER_HAS_DOCUMENT_TYPE_ANY,
FILTER_HAS_STORAGE_PATH_ANY,
FILTER_HAS_TAGS_ANY,
} from 'src/app/data/filter-rule-type'
import { DataType } from 'src/app/data/datatype'
import { ObjectWithId } from 'src/app/data/object-with-id'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
PermissionsService,
PermissionAction,
} from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import {
GlobalSearchResult,
SearchService,
} from 'src/app/services/rest/search.service'
import { ToastService } from 'src/app/services/toast.service'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { CustomFieldEditDialogComponent } from '../../common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
import { MailAccountEditDialogComponent } from '../../common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
import { MailRuleEditDialogComponent } from '../../common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component'
import { HotKeyService } from 'src/app/services/hot-key.service'
@Component({
selector: 'pngx-global-search',
templateUrl: './global-search.component.html',
styleUrl: './global-search.component.scss',
})
export class GlobalSearchComponent implements OnInit {
public DataType = DataType
public query: string
public queryDebounce: Subject<string>
public searchResults: GlobalSearchResult
private currentItemIndex: number = -1
private domIndex: number = -1
public loading: boolean = false
@ViewChild('searchInput') searchInput: ElementRef
@ViewChild('resultsDropdown') resultsDropdown: NgbDropdown
@ViewChildren('resultItem') resultItems: QueryList<ElementRef>
@ViewChildren('primaryButton') primaryButtons: QueryList<ElementRef>
@ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef>
constructor(
public searchService: SearchService,
private router: Router,
private modalService: NgbModal,
private documentService: DocumentService,
private documentListViewService: DocumentListViewService,
private permissionsService: PermissionsService,
private toastService: ToastService,
private hotkeyService: HotKeyService
) {
this.queryDebounce = new Subject<string>()
this.queryDebounce
.pipe(
debounceTime(400),
map((query) => query?.trim()),
filter((query) => !query?.length || query?.length > 2),
distinctUntilChanged()
)
.subscribe((text) => {
this.query = text
if (text) this.search(text)
})
}
ngOnInit() {
this.hotkeyService
.addShortcut({ keys: '/', description: $localize`Global search` })
.subscribe(() => {
this.searchInput.nativeElement.focus()
})
}
private search(query: string) {
this.loading = true
this.searchService.globalSearch(query).subscribe((results) => {
this.searchResults = results
this.loading = false
this.resultsDropdown.open()
})
}
public primaryAction(type: string, object: ObjectWithId) {
this.reset(true)
let filterRuleType: number
let editDialogComponent: any
let size: string = 'md'
switch (type) {
case DataType.Document:
this.router.navigate(['/documents', object.id])
return
case DataType.SavedView:
this.router.navigate(['/view', object.id])
return
case DataType.Correspondent:
filterRuleType = FILTER_HAS_CORRESPONDENT_ANY
break
case DataType.DocumentType:
filterRuleType = FILTER_HAS_DOCUMENT_TYPE_ANY
break
case DataType.StoragePath:
filterRuleType = FILTER_HAS_STORAGE_PATH_ANY
break
case DataType.Tag:
filterRuleType = FILTER_HAS_TAGS_ANY
break
case DataType.User:
editDialogComponent = UserEditDialogComponent
size = 'lg'
break
case DataType.Group:
editDialogComponent = GroupEditDialogComponent
size = 'lg'
break
case DataType.MailAccount:
editDialogComponent = MailAccountEditDialogComponent
size = 'xl'
break
case DataType.MailRule:
editDialogComponent = MailRuleEditDialogComponent
size = 'xl'
break
case DataType.CustomField:
editDialogComponent = CustomFieldEditDialogComponent
break
case DataType.Workflow:
editDialogComponent = WorkflowEditDialogComponent
size = 'xl'
break
}
if (filterRuleType) {
this.documentListViewService.quickFilter([
{ rule_type: filterRuleType, value: object.id.toString() },
])
} else if (editDialogComponent) {
const modalRef: NgbModalRef = this.modalService.open(
editDialogComponent,
{ size }
)
modalRef.componentInstance.dialogMode = EditDialogMode.EDIT
modalRef.componentInstance.object = object
modalRef.componentInstance.succeeded.subscribe(() => {
this.toastService.showInfo($localize`Successfully updated object.`)
})
modalRef.componentInstance.failed.subscribe((e) => {
this.toastService.showError($localize`Error occurred saving object.`, e)
})
}
}
public secondaryAction(type: string, object: ObjectWithId) {
this.reset(true)
let editDialogComponent: any
let size: string = 'md'
switch (type) {
case DataType.Document:
window.open(this.documentService.getDownloadUrl(object.id))
break
case DataType.Correspondent:
editDialogComponent = CorrespondentEditDialogComponent
break
case DataType.DocumentType:
editDialogComponent = DocumentTypeEditDialogComponent
break
case DataType.StoragePath:
editDialogComponent = StoragePathEditDialogComponent
break
case DataType.Tag:
editDialogComponent = TagEditDialogComponent
break
}
if (editDialogComponent) {
const modalRef: NgbModalRef = this.modalService.open(
editDialogComponent,
{ size }
)
modalRef.componentInstance.dialogMode = EditDialogMode.EDIT
modalRef.componentInstance.object = object
modalRef.componentInstance.succeeded.subscribe(() => {
this.toastService.showInfo($localize`Successfully updated object.`)
})
modalRef.componentInstance.failed.subscribe((e) => {
this.toastService.showError($localize`Error occurred saving object.`, e)
})
}
}
private reset(close: boolean = false) {
this.queryDebounce.next(null)
this.searchResults = null
this.currentItemIndex = -1
if (close) {
this.resultsDropdown.close()
}
}
private setCurrentItem() {
// QueryLists do not always reflect the current DOM order, so we need to find the actual element
// Yes, using some vanilla JS
const result: HTMLElement = this.resultItems.first.nativeElement.parentNode
.querySelectorAll('.dropdown-item')
.item(this.currentItemIndex)
this.domIndex = this.resultItems
.toArray()
.indexOf(this.resultItems.find((item) => item.nativeElement === result))
const item: ElementRef = this.primaryButtons.get(this.domIndex)
item.nativeElement.focus()
}
onItemHover(event: MouseEvent) {
const item: ElementRef = this.resultItems
.toArray()
.find((item) => item.nativeElement === event.currentTarget)
this.currentItemIndex = this.resultItems.toArray().indexOf(item)
this.setCurrentItem()
}
onButtonHover(event: MouseEvent) {
;(event.currentTarget as HTMLElement).focus()
}
public searchInputKeyDown(event: KeyboardEvent) {
if (
event.key === 'ArrowDown' &&
this.searchResults?.total &&
this.resultsDropdown.isOpen()
) {
event.preventDefault()
this.currentItemIndex = 0
this.setCurrentItem()
} else if (
event.key === 'ArrowUp' &&
this.searchResults?.total &&
this.resultsDropdown.isOpen()
) {
event.preventDefault()
this.currentItemIndex = this.searchResults.total - 1
this.setCurrentItem()
} else if (
event.key === 'Enter' &&
this.searchResults?.total === 1 &&
this.resultsDropdown.isOpen()
) {
this.primaryButtons.first.nativeElement.click()
this.searchInput.nativeElement.blur()
} else if (
event.key === 'Enter' &&
this.searchResults?.total &&
!this.resultsDropdown.isOpen()
) {
this.resultsDropdown.open()
} else if (event.key === 'Escape' && !this.resultsDropdown.isOpen()) {
if (this.query?.length) {
this.reset(true)
} else {
this.searchInput.nativeElement.blur()
}
}
}
dropdownKeyDown(event: KeyboardEvent) {
if (
this.searchResults?.total &&
this.resultsDropdown.isOpen() &&
document.activeElement !== this.searchInput.nativeElement
) {
if (event.key === 'ArrowDown') {
event.preventDefault()
event.stopImmediatePropagation()
if (this.currentItemIndex < this.searchResults.total - 1) {
this.currentItemIndex++
this.setCurrentItem()
} else {
this.searchInput.nativeElement.focus()
this.currentItemIndex = -1
}
} else if (event.key === 'ArrowUp') {
event.preventDefault()
event.stopImmediatePropagation()
if (this.currentItemIndex > 0) {
this.currentItemIndex--
this.setCurrentItem()
} else {
this.searchInput.nativeElement.focus()
this.currentItemIndex = -1
}
} else if (event.key === 'ArrowRight') {
event.preventDefault()
event.stopImmediatePropagation()
this.secondaryButtons.get(this.domIndex)?.nativeElement.focus()
} else if (event.key === 'ArrowLeft') {
event.preventDefault()
event.stopImmediatePropagation()
this.primaryButtons.get(this.domIndex).nativeElement.focus()
}
}
}
onButtonKeyDown(event: KeyboardEvent) {
// prevents ngBootstrap issue with keydown events
if (
!['ArrowDown', 'ArrowUp', 'ArrowRight', 'ArrowLeft'].includes(event.key)
) {
event.stopImmediatePropagation()
}
}
public onDropdownOpenChange(open: boolean) {
if (!open) {
this.reset()
}
}
public disablePrimaryButton(type: DataType, object: ObjectWithId): boolean {
if (
[
DataType.Workflow,
DataType.CustomField,
DataType.Group,
DataType.User,
].includes(type)
) {
return !this.permissionsService.currentUserHasObjectPermissions(
PermissionAction.Change,
object
)
}
return false
}
public disableSecondaryButton(type: DataType, object: ObjectWithId): boolean {
if (DataType.Document === type) {
return false
}
return !this.permissionsService.currentUserHasObjectPermissions(
PermissionAction.Change,
object
)
}
runAdvanedSearch() {
this.documentListViewService.quickFilter([
{ rule_type: FILTER_FULLTEXT_QUERY, value: this.query },
])
this.reset(true)
}
}

View File

@@ -1,9 +1,11 @@
<button *ngIf="active" class="position-absolute top-0 start-100 translate-middle badge bg-secondary border border-light rounded-pill p-1" title="Clear" i18n-title (click)="onClick($event)">
<svg *ngIf="!isNumbered && selected" width="1em" height="1em" class="check m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<use xlink:href="assets/bootstrap-icons.svg#check-lg"/>
</svg>
<div *ngIf="isNumbered" class="number">{{number}}<span class="visually-hidden">selected</span></div>
<svg width=".9em" height="1em" class="x m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<use xlink:href="assets/bootstrap-icons.svg#x-lg"/>
</svg>
</button>
@if (active) {
<button class="position-absolute top-0 start-100 translate-middle badge bg-secondary border border-light rounded-pill p-1" title="Clear" i18n-title (click)="onClick($event)">
@if (!isNumbered && selected) {
<i-bs class="check" width="1em" height="1em" name="check-lg"></i-bs>
}
@if (isNumbered) {
<div class="number">{{number}}<span class="visually-hidden">selected</span></div>
}
<i-bs class="x" width=".9em" height="1em" name="x-lg"></i-bs>
</button>
}

View File

@@ -22,7 +22,7 @@ button:hover {
.x {
display: inline-block;
position: absolute;
top: 5px;
left: calc(50% - 4px);
top: .4em;
left: calc(50% - .4em);
}
}

View File

@@ -1,5 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ClearableBadgeComponent } from './clearable-badge.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
describe('ClearableBadgeComponent', () => {
let component: ClearableBadgeComponent
@@ -8,6 +9,7 @@ describe('ClearableBadgeComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ClearableBadgeComponent],
imports: [NgxBootstrapIconsModule.pick(allIcons)],
}).compileComponents()
fixture = TestBed.createComponent(ClearableBadgeComponent)

View File

@@ -0,0 +1,22 @@
<button
type="button"
class="btn {{buttonClasses}}"
(click)="onClick($event)"
[disabled]="disabled"
[ngbPopover]="popoverContent"
[autoClose]="true"
(hidden)="confirming = false"
#popover="ngbPopover"
popoverClass="popover-slim"
>
@if (iconName) {
<i-bs [class.me-1]="label" name="{{iconName}}"></i-bs>
}
<ng-container>{{label}}</ng-container>
</button>
<ng-template #popoverContent>
<div>
{{confirmMessage}}&nbsp;<button class="btn btn-link btn-sm text-danger p-0 m-0 lh-1" type="button" (click)="onConfirm($event)">Yes</button>
</div>
</ng-template>

View File

@@ -0,0 +1,12 @@
// Taken from bootstrap rules, obv
::ng-deep .input-group > pngx-confirm-button:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) > button,
::ng-deep .btn-group > pngx-confirm-button:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) > button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
::ng-deep .input-group:not(.has-validation) > pngx-confirm-button:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating) > button,
::ng-deep .btn-group:not(.has-validation) > pngx-confirm-button:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating) > button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

View File

@@ -0,0 +1,37 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { ConfirmButtonComponent } from './confirm-button.component'
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
describe('ConfirmButtonComponent', () => {
let component: ConfirmButtonComponent
let fixture: ComponentFixture<ConfirmButtonComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ConfirmButtonComponent],
imports: [NgbPopoverModule, NgxBootstrapIconsModule.pick(allIcons)],
}).compileComponents()
fixture = TestBed.createComponent(ConfirmButtonComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should show confirm on click', () => {
expect(component.popover.isOpen()).toBeFalsy()
expect(component.confirming).toBeFalsy()
component.onClick(new MouseEvent('click'))
expect(component.popover.isOpen()).toBeTruthy()
expect(component.confirming).toBeTruthy()
})
it('should emit confirm on confirm', () => {
const confirmSpy = jest.spyOn(component.confirm, 'emit')
component.onConfirm(new MouseEvent('click'))
expect(confirmSpy).toHaveBeenCalled()
expect(component.popover.isOpen()).toBeFalsy()
expect(component.confirming).toBeFalsy()
})
})

View File

@@ -0,0 +1,55 @@
import {
Component,
EventEmitter,
Input,
Output,
ViewChild,
} from '@angular/core'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
@Component({
selector: 'pngx-confirm-button',
templateUrl: './confirm-button.component.html',
styleUrl: './confirm-button.component.scss',
})
export class ConfirmButtonComponent {
@Input()
label: string
@Input()
confirmMessage: string = $localize`Are you sure?`
@Input()
buttonClasses: string = 'btn-primary'
@Input()
iconName: string
@Input()
disabled: boolean = false
@Output()
confirm: EventEmitter<void> = new EventEmitter<void>()
@ViewChild('popover') popover: NgbPopover
public confirming: boolean = false
public onClick(event: MouseEvent) {
if (!this.confirming) {
this.confirming = true
this.popover.open()
}
event.preventDefault()
event.stopImmediatePropagation()
}
public onConfirm(event: MouseEvent) {
this.confirm.emit()
this.confirming = false
event.preventDefault()
event.stopImmediatePropagation()
}
}

View File

@@ -1,24 +1,32 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
<div class="modal-body">
@if (messageBold) {
<p><b>{{messageBold}}</b></p>
}
@if (message) {
<p class="mb-0" [innerHTML]="message | safeHtml"></p>
}
</div>
<div class="modal-footer">
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
</button>
</div>
<div class="modal-body">
<p *ngIf="messageBold"><b>{{messageBold}}</b></p>
<p class="mb-0" *ngIf="message" [innerHTML]="message | safeHtml"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" [disabled]="!buttonsEnabled" i18n>
<span class="d-inline-block" style="padding-bottom: 1px;" >Cancel</span>
</button>
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
<span>
{{btnCaption}}
<span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
</span>
<ngb-progressbar *ngIf="!confirmButtonEnabled" style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
</button>
<button *ngIf="alternativeBtnCaption" type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled">
{{alternativeBtnCaption}}
</button>
</div>
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
<span>
{{btnCaption}}
<span class="visually-hidden">{{ seconds | number: '1.0-0' }} seconds</span>
</span>
@if (!confirmButtonEnabled) {
<ngb-progressbar style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
}
</button>
@if (alternativeBtnCaption) {
<button type="button" class="btn" [class]="alternativeBtnClass" (click)="alternative()" [disabled]="!alternativeButtonEnabled || !buttonsEnabled">
{{alternativeBtnCaption}}
</button>
}
</div>

View File

@@ -37,6 +37,12 @@ export class ConfirmDialogComponent {
@Input()
alternativeBtnCaption
@Input()
cancelBtnClass = 'btn-outline-secondary'
@Input()
cancelBtnCaption = $localize`Cancel`
@Input()
buttonsEnabled = true

View File

@@ -0,0 +1,39 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()">
</button>
</div>
<div class="modal-body">
<p>{{message}}</p>
<div class="form-group">
<label class="form-label" for="metadataDocumentID" i18n>Documents:</label>
<ul class="list-group"
cdkDropList
(cdkDropListDropped)="onDrop($event)">
@for (documentID of documentIDs; track documentID) {
<li class="list-group-item" cdkDrag>
<i-bs name="grip-vertical" class="me-2"></i-bs>
{{getDocument(documentID)?.title}}
</li>
}
</ul>
</div>
<div class="form-group mt-4">
<label class="form-label" for="metadataDocumentID" i18n>Use metadata from:</label>
<select class="form-select" [(ngModel)]="metadataDocumentID">
<option [ngValue]="-1" i18n>Regenerate all metadata</option>
@for (document of documents; track document.id) {
<option [ngValue]="document.id">{{document.title}}</option>
}
</select>
</div>
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be included.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
</button>
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
{{btnCaption}}
</button>
</div>

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