Compare commits

..

255 Commits

Author SHA1 Message Date
shamoon
e1b9434645 Just messing around
[ci skip]
2025-01-26 21:51:57 -08:00
shamoon
38296d9426 Fix: set larger page size for abstract service getFew (#8920) 2025-01-26 12:25:50 -08:00
shamoon
8311313e6e Nit: use camelCase 2025-01-25 13:28:08 -08:00
shamoon
ac292999ef Dev: fix utif bailout warning 2025-01-25 13:22:07 -08:00
shamoon
f3cda54cd1 Fix/refactor: remove doc observables, fix username async (#8908) 2025-01-25 12:38:36 -08:00
shamoon
8f9a294529 Fix: include missing fields for saved view widgets (#8905) 2025-01-25 07:49:14 -08:00
Trenton H
702de0cac3 Chore: Upgrades dependencies and hook versions (#8895) 2025-01-24 12:08:58 -08:00
shamoon
ca42762841 Fix: force set document not dirty before close after save (#8888) 2025-01-23 17:51:01 -08:00
shamoon
13cfd6f904 Change: Revert dropdown sorting by doc count (#8887) 2025-01-23 15:52:35 -08:00
shamoon
18c4e6029f Fixhancement: restore search highlighting and add for built-in viewer (#8885) 2025-01-23 15:00:46 -08:00
shamoon
6c34e37838 Fix: resolve cpu usage due to incorrect interval use (#8884) 2025-01-23 10:37:20 -08:00
github-actions[bot]
2c28348b56 Changelog v2.14.5 - GHA (#8852)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-21 17:50:53 -08:00
shamoon
79e541244e Bump version to 2.14.5 2025-01-21 17:02:38 -08:00
shamoon
74afad5976 Merge branch 'dev' 2025-01-21 17:02:07 -08:00
dependabot[bot]
c694c9791b Chore(deps-dev): Bump undici from 5.28.4 to 5.28.5 in /src-ui (#8851)
Bumps [undici](https://github.com/nodejs/undici) from 5.28.4 to 5.28.5.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.4...v5.28.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-22 00:38:44 +00:00
github-actions[bot]
11ceb8bde5 New Crowdin translations by GitHub Action (#8804) 2025-01-21 16:28:36 -08:00
dependabot[bot]
20ec8cb57b Chore(deps-dev): Bump the development group with 2 updates (#8841)
* 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.8.6 to 0.9.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/0.8.6...0.9.2)

Updates `mkdocs-material` from 9.5.49 to 9.5.50
- [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.49...9.5.50)

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

* Update .pre-commit-config.yaml

* Run new ruff format

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-21 19:22:25 +00:00
shamoon
bfc11a545b Change: use simpler method for attaching files (#8845) 2025-01-21 10:38:21 -08:00
shamoon
4866af31cb Documentation: update usage 2025-01-20 22:51:12 -08:00
shamoon
0ea4da03a7 Chore: fix coverage for superuser change 2025-01-20 12:02:20 -08:00
shamoon
41bcc12cc2 Change: restrict altering and creation of superusers to superusers only (#8837) 2025-01-20 11:57:22 -08:00
shamoon
475c231c6f Fix: fix long tag visual wrapping (#8833) 2025-01-20 07:43:39 -08:00
shamoon
e00dd46b22 Change: allow generate auth token without a usable password (#8824) 2025-01-19 13:49:16 -08:00
Trenton H
fd425aa618 Fix: Enforce classifier training ordering to prevent extra training (#8822) 2025-01-19 20:52:03 +00:00
shamoon
e1dde85c59 Fix failing test 2025-01-19 10:46:57 -08:00
shamoon
01207a284d Fix: import router module to not found component (#8821) 2025-01-19 10:37:31 -08:00
Thomas Hess
0f863ab378 Documentation: fix error in storage paths example in advanced_usage.md (#8817) 2025-01-19 18:04:55 +00:00
shamoon
258064b339 Fix: better reflect some mail account / rule permissions in UI (#8812) 2025-01-19 08:50:26 -08:00
kevin
2bcb37f3e9 Documentation: Add ISO number example (#8809) 2025-01-19 14:53:51 +00:00
github-actions[bot]
81f8c64b2c Changelog v2.14.4 - GHA (#8801)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-18 13:05:36 -08:00
shamoon
3b80112521 Bump version to 2.14.4 2025-01-18 12:27:08 -08:00
shamoon
459feea31e Merge branch 'dev' 2025-01-18 12:26:40 -08:00
github-actions[bot]
eea5839390 New Crowdin translations by GitHub Action (#8800)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-18 12:26:08 -08:00
github-actions[bot]
c79414ebd9 New Crowdin translations by GitHub Action (#8772)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-18 12:24:14 -08:00
shamoon
ed1775e689 Enhancement: allow specifying JSON encoding for webhooks (#8799) 2025-01-18 12:19:50 -08:00
shamoon
cd50f20a20 Fix: its paths not pathes 2025-01-18 07:43:02 -08:00
shamoon
c8ec70c05f Chore: update issue config for new GH issue workflow 2025-01-17 23:16:03 -08:00
shamoon
5e3ee3a80d Fix: disable API basic auth if MFA enabled (#8792) 2025-01-18 03:51:53 +00:00
Colin Yates
29726c3ce1 Documentation: note wget requirement for install script on macOS (#8793)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-18 00:46:47 +00:00
Trenton H
6804c92861 Fix: Include email and webhook objects in the export (#8790) 2025-01-17 13:00:59 -08:00
shamoon
a32077566b Fix: use MIMEBase for email attachments (#8762) 2025-01-16 18:48:19 +00:00
shamoon
283bcb4c91 Fix: handle page out of range in mgmt lists after delete (#8771) 2025-01-16 00:59:27 -08:00
shamoon
f68ee628d9 Refactor: consolidate original_file logic in wf handler 2025-01-15 22:13:40 -08:00
shamoon
bd5ba97ee8 Merge branch 'main' into dev 2025-01-15 21:40:56 -08:00
github-actions[bot]
325594034e Changelog v2.14.3 - GHA (#8761)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-15 21:26:20 -08:00
shamoon
28261ac51c Bump version to 2.14.3 2025-01-15 21:01:02 -08:00
github-actions[bot]
23ef52f405 New Crowdin translations by GitHub Action (#8760)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-15 20:41:57 -08:00
github-actions[bot]
2b45793bc2 New Crowdin translations by GitHub Action (#8759) 2025-01-15 20:39:00 -08:00
Trenton H
2f96cc0050 Adds a default 30s timeout for emails, instead of no timeout (#8757) 2025-01-15 21:46:37 +00:00
Max Mehl
d36e8254f3 Enhancement: set autofocus on MFA code field (#8756) 2025-01-15 21:32:55 +00:00
shamoon
405fab8514 Update handlers.py 2025-01-15 12:18:02 -08:00
shamoon
ee4f62a1b3 Fix: import forms modules for entries component (#8752) 2025-01-15 08:46:11 -08:00
shamoon
d97e4a9a95 Fix: fix email/wh actions on consume started (#8750) 2025-01-15 15:48:10 +00:00
shamoon
1cfba87114 Fix: import date picker module in cf query dropdown (#8749) 2025-01-15 06:46:36 -08:00
github-actions[bot]
b145ed315a Documentation: Add v2.14.2 changelog (#8743)
* Changelog v2.14.2 - GHA

* Update changelog.md

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-14 23:42:18 -08:00
shamoon
1f47b8c090 Bump version to 2.14.2 2025-01-14 23:18:12 -08:00
shamoon
1e3f2a1438 Fix: dont try to parse empty params (#8742) 2025-01-14 23:14:20 -08:00
shamoon
d61b2bbfc6 Fix: pass working file to workflows, pickle file bytes (#8741) 2025-01-14 23:03:40 -08:00
github-actions[bot]
e1d6b4a9ac New Crowdin translations by GitHub Action (#8724)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-14 17:13:03 -08:00
dependabot[bot]
9f398337c6 Chore(deps): Bump django from 5.1.4 to 5.1.5 (#8738)
Bumps [django](https://github.com/django/django) from 5.1.4 to 5.1.5.
- [Commits](https://github.com/django/django/compare/5.1.4...5.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-15 01:02:23 +00:00
shamoon
765bf1d11c Fix: use hard delete when bulk editing custom fields (#8740) 2025-01-14 16:47:29 -08:00
Trenton H
49a96ccee0 Lock the media directory and refresh the document to ensure we have where it actually lives before attaching (#8737) 2025-01-14 23:18:10 +00:00
shamoon
c45bbcaea2 Fix: include tooltip module for custom fields display (#8739) 2025-01-14 15:03:56 -08:00
shamoon
ab87aedfc7 Fix: remove id of webhook/email actions on copy (#8729) 2025-01-14 05:44:40 -08:00
shamoon
18e3ad8b22 Fix: import dnd module for merge confirm dialog (#8727) 2025-01-14 05:35:45 -08:00
shamoon
c342eafa8d Documentation: fix link 2025-01-13 18:05:47 -08:00
github-actions[bot]
290c44f5d5 Documentation: Add v2.14.1 changelog (#8714)
---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-13 18:04:22 -08:00
shamoon
02015ec404 Bump version to 2.14.1 2025-01-13 17:16:52 -08:00
shamoon
98b4f447d8 Merge branch 'dev' 2025-01-13 17:16:20 -08:00
github-actions[bot]
272691e386 New Crowdin translations by GitHub Action (#8700)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-13 16:41:24 -08:00
shamoon
1012cee39a Fix: include reprocess in MODIFIED_FIELD_BY_METHOD (#8710) 2025-01-13 14:12:02 -08:00
shamoon
19a5733d0d Fix: include tag component in list view (#8706) 2025-01-13 13:21:16 -08:00
shamoon
86788f1445 Fix: use unmodified original for checksum if exists (#8693) 2025-01-13 21:02:10 +00:00
shamoon
1d5e7e930e Fix: complete load with native PDF viewer (#8699) 2025-01-13 09:53:39 -08:00
github-actions[bot]
da6d568906 Changelog v2.14.0 - GHA (#8694)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2025-01-13 09:51:25 -08:00
shamoon
abf05d5ebe Bump version to 2.14.0 2025-01-13 07:47:25 -08:00
shamoon
5821033e3d Merge branch 'dev' 2025-01-13 07:47:08 -08:00
github-actions[bot]
5c1039d07d New Crowdin translations by GitHub Action (#8692)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-13 07:45:40 -08:00
github-actions[bot]
b874d6d1a4 New Crowdin translations by GitHub Action (#8691)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-01-13 07:34:10 -08:00
github-actions[bot]
68a47cbf0b New Crowdin translations by GitHub Action (#8178) 2025-01-13 07:32:04 -08:00
Michael Williams
ccc8917706 Documentation: minor formatting fix in config (#8683) 2025-01-13 07:26:02 +00:00
shamoon
4c4d3a45c2 Fix: fix jittering when toggling workflows 2025-01-11 22:42:24 -08:00
shamoon
6007b252d7 Fix: missing icon import, fix bulk edit cf messages 2025-01-11 22:25:12 -08:00
shamoon
3f05b9f921 Change: tweak some settings alignment 2025-01-11 17:55:19 -08:00
shamoon
67ec1aea42 Fix: add missing dropdown component to tasks 2025-01-09 22:49:31 -08:00
shamoon
f5a0b9c174 Fix: random styling fixes 2025-01-09 22:49:30 -08:00
Fabian Krüger
7035445d6a Documentation: Add hint to set Content-Length header for post document (#8639) 2025-01-08 04:13:24 +00:00
shamoon
a899ff16e3 Fix: use state param with oauth (#8636) 2025-01-08 03:48:36 +00:00
shamoon
d77c9e3bd5 Update configuration.md 2025-01-07 19:07:01 -08:00
shamoon
0ab21b6fc5 Fix: allow document editor to create share link 2025-01-07 07:59:35 -08:00
shamoon
485237caf1 Fix: check permissions for all documents via bulk download (#8631) 2025-01-07 00:10:11 -08:00
dependabot[bot]
9181aebc8c Chore(deps): Bump the small-changes group with 3 updates (#8627)
Bumps the small-changes group with 3 updates: [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF), [pathvalidate](https://github.com/thombashi/pathvalidate) and [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp).


Updates `ocrmypdf` from 16.7.0 to 16.8.0
- [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/v16.7.0...v16.8.0)

Updates `pathvalidate` from 3.2.1 to 3.2.3
- [Release notes](https://github.com/thombashi/pathvalidate/releases)
- [Changelog](https://github.com/thombashi/pathvalidate/blob/master/CHANGELOG.md)
- [Commits](https://github.com/thombashi/pathvalidate/compare/v3.2.1...v3.2.3)

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

---
updated-dependencies:
- dependency-name: ocrmypdf
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: pathvalidate
  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>
2025-01-07 01:32:36 +00:00
dependabot[bot]
eec3f13610 Chore(deps-dev): Bump ruff from 0.8.4 to 0.8.6 in the development group (#8626)
* Chore(deps-dev): Bump ruff from 0.8.4 to 0.8.6 in the development group

Bumps the development group with 1 update: [ruff](https://github.com/astral-sh/ruff).


Updates `ruff` from 0.8.4 to 0.8.6
- [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/0.8.4...0.8.6)

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

* Update .pre-commit-config.yaml

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-07 01:15:43 +00:00
Sebastian Steinbeißer
935d077836 Chore: Switch from os.path to pathlib.Path (#8325)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-01-06 12:12:27 -08:00
tsia
d06aac947d Chore: disable max-age for some document endpoints (#8611) 2025-01-06 18:36:04 +00:00
shamoon
1856837d21 Fix: more missing modules 2025-01-05 12:37:10 -08:00
shamoon
bac6a013ed Fix: add missing CustomFieldDisplayComponent to saved view widget
Closes #8614
2025-01-05 12:28:10 -08:00
shamoon
aef68f0b41 Fix: fix saved views triggering changeafterchecked warning 2025-01-04 13:24:35 -08:00
shamoon
7fbe4d0aad Fix: found a couple more missing components 2025-01-04 13:24:35 -08:00
Malte Breitzmann
2a1c6bf0ca Documentation: remove dollar sign in code snippets (#8600) 2025-01-04 00:30:25 +00:00
shamoon
8e6de2790e Fix: do not accept empty string for doc link value via API (#8596) 2025-01-03 15:16:00 -08:00
shamoon
94a20b4510 Fix: prevent saved view widgets cut off on mobile 2025-01-02 19:50:44 -08:00
shamoon
21558dcf8b Fix: add missing ngbdropdown for management lists on mobile 2025-01-02 16:26:26 -08:00
shamoon
85d913520b Fix: add one missing bs class 2025-01-01 22:31:45 -08:00
shamoon
f89b6281da Enhancement: angular 19 (#8584) 2025-01-01 22:26:53 -08:00
shamoon
75de53eb83 Fix relative date again 2025-01-01 21:49:51 -08:00
shamoon
b0dd77bfd8 Fix: fix hotkey arrows (#8583) 2025-01-01 21:10:56 -08:00
shamoon
51b0f6e325 Fix: remove outdated admin logentry handler (#8580) 2025-01-01 10:11:54 -08:00
shamoon
9153be489c Fix: tweak relative date 2025-01-01 09:47:27 -08:00
shamoon
90731e05f5 Fix: remove accidental console log 2024-12-30 23:11:53 -08:00
dependabot[bot]
9270f7290e Chore(deps): Bump django-allauth in the django group (#8574)
Bumps the django group with 1 update: [django-allauth](https://github.com/sponsors/pennersr).


Updates `django-allauth` from 65.3.0 to 65.3.1
- [Commits](https://github.com/sponsors/pennersr/commits)

---
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-12-30 14:44:20 -08:00
shamoon
4e3d25c714 Enhancement: custom field sorting (#8494) 2024-12-30 18:18:34 +00:00
shamoon
e44cfef662 Documentation: cleanup devcontainer docs and move into tasks and launch config files 2024-12-26 10:49:32 -08:00
Orce MARINKOVSKI
6c138a21d4 Development: devcontainer improvements (#8553)
---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-12-26 16:10:02 +00:00
Orce MARINKOVSKI
b5a1dc86a5 Development: fix devcontainer postCreateCommand (#8542) 2024-12-25 08:53:30 +00:00
shamoon
3520a83c2f Fix: fix occasional error toast overflow (#8552) 2024-12-24 23:11:48 -08:00
shamoon
93f2ef45f5 Fix: fix share link archive version detection (#8551) 2024-12-24 23:11:03 -08:00
shamoon
8291ec17d4 Fix: disable email workflow type if email not enabled 2024-12-24 09:31:59 -08:00
dependabot[bot]
5007855904 Chore(deps-dev): Bump ruff from 0.8.3 to 0.8.4 in the development group (#8546)
* Chore(deps-dev): Bump ruff from 0.8.3 to 0.8.4 in the development group

Bumps the development group with 1 update: [ruff](https://github.com/astral-sh/ruff).


Updates `ruff` from 0.8.3 to 0.8.4
- [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/0.8.3...0.8.4)

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

* Update .pre-commit-config.yaml

---------

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-12-23 22:07:57 +00:00
dependabot[bot]
754cf17c90 Chore(deps): Bump the small-changes group with 6 updates (#8547)
Bumps the small-changes group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [gotenberg-client](https://github.com/stumpylog/gotenberg-client) | `0.7.0` | `0.8.2` |
| [httpx-oauth](https://github.com/frankie567/httpx-oauth) | `0.16.0` | `0.16.1` |
| [imap-tools](https://github.com/ikvk/imap_tools) | `1.7.4` | `1.8.0` |
| [jinja2](https://github.com/pallets/jinja) | `3.1.4` | `3.1.5` |
| [rapidfuzz](https://github.com/rapidfuzz/RapidFuzz) | `3.10.1` | `3.11.0` |
| [tika-client](https://github.com/stumpylog/tika-rest-client) | `0.7.0` | `0.8.1` |


Updates `gotenberg-client` from 0.7.0 to 0.8.2
- [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.7.0...0.8.2)

Updates `httpx-oauth` from 0.16.0 to 0.16.1
- [Release notes](https://github.com/frankie567/httpx-oauth/releases)
- [Commits](https://github.com/frankie567/httpx-oauth/compare/v0.16.0...v0.16.1)

Updates `imap-tools` from 1.7.4 to 1.8.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.7.4...v1.8.0)

Updates `jinja2` from 3.1.4 to 3.1.5
- [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.4...3.1.5)

Updates `rapidfuzz` from 3.10.1 to 3.11.0
- [Release notes](https://github.com/rapidfuzz/RapidFuzz/releases)
- [Changelog](https://github.com/rapidfuzz/RapidFuzz/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/rapidfuzz/RapidFuzz/compare/v3.10.1...v3.11.0)

Updates `tika-client` from 0.7.0 to 0.8.1
- [Release notes](https://github.com/stumpylog/tika-rest-client/releases)
- [Changelog](https://github.com/stumpylog/tika-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stumpylog/tika-rest-client/compare/0.7.0...0.8.1)

---
updated-dependencies:
- dependency-name: gotenberg-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: httpx-oauth
  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: jinja2
  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: tika-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-12-23 13:48:21 -08:00
shamoon
6972d0337f Fix: fix some popover closing behavior 2024-12-21 21:04:55 -08:00
HiranChaudhuri
452ea2ccf9 Enhancement: add timeout for Tika client (#8520)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-12-19 16:58:26 +00:00
shamoon
2bcbed31e9 Fix: add some minor frontend permissions checks (#8524) 2024-12-19 08:08:46 -08:00
shamoon
20b7ff9f9f Fix: resolve test warning 2024-12-19 08:05:06 -08:00
dependabot[bot]
3f7a0802a4 Chore(deps-dev): Bump the development group with 2 updates (#8502)
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.8.2 to 0.8.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/0.8.2...0.8.3)

Updates `mkdocs-material` from 9.5.48 to 9.5.49
- [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.48...9.5.49)

---
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-12-16 13:53:12 -08:00
shamoon
ea514a7ed8 FIx: obliquely trim spaces from global search (#8484) 2024-12-13 07:39:16 -08:00
shamoon
af67dbe523 Update frontend strings 2024-12-13 07:36:41 -08:00
shamoon
8cd09ba10d Chore: add prettier organize imports 2024-12-13 00:45:20 -08:00
shamoon
9e4bc05a24 Fix: include global perms for bulk edit endpoint (#8468) 2024-12-12 15:38:54 +00:00
shamoon
dafb0b1f21 Enhancement: process mail button (#8466) 2024-12-11 11:03:53 -08:00
shamoon
2ac2a6dec6 Fix: frontend better reflect global perms for bulk edit, disabled form state (#8469) 2024-12-11 01:05:23 -08:00
shamoon
beb8ed8313 Fix: minor safari visual fixes around loading cards 2024-12-10 08:49:18 -08:00
shamoon
997db0fea1 Chore: update ng2 pdf viewer (#8462) 2024-12-09 16:12:53 -08:00
dependabot[bot]
64d40a7c37 Chore(deps-dev): Bump the development group with 2 updates (#8458)
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.8.1 to 0.8.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/0.8.1...0.8.2)

Updates `mkdocs-material` from 9.5.47 to 9.5.48
- [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.47...9.5.48)

---
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-12-09 22:56:28 +00:00
dependabot[bot]
dc422b5dac Chore(deps): Bump django-soft-delete from 1.0.15 to 1.0.16 in the django group (#8459)
* Chore(deps): Bump django-soft-delete in the django group

Bumps the django group with 1 update: [django-soft-delete](https://github.com/san4ezy/django_softdelete).


Updates `django-soft-delete` from 1.0.15 to 1.0.16
- [Commits](https://github.com/san4ezy/django_softdelete/commits)

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

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 21:48:54 +00:00
dependabot[bot]
f56ab150b0 Chore(deps): Bump the small-changes group with 4 updates (#8460)
* Chore(deps): Bump the small-changes group with 4 updates

Bumps the small-changes group with 4 updates: [httpx-oauth](https://github.com/frankie567/httpx-oauth), [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF), [redis](https://github.com/redis/redis-py) and [scikit-learn](https://github.com/scikit-learn/scikit-learn).


Updates `httpx-oauth` from 0.15.1 to 0.16.0
- [Release notes](https://github.com/frankie567/httpx-oauth/releases)
- [Commits](https://github.com/frankie567/httpx-oauth/compare/v0.15.1...v0.16.0)

Updates `ocrmypdf` from 16.6.2 to 16.7.0
- [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/v16.6.2...v16.7.0)

Updates `redis` from 5.2.0 to 5.2.1
- [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.2.0...v5.2.1)

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

---
updated-dependencies:
- dependency-name: httpx-oauth
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: ocrmypdf
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
- dependency-name: redis
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: scikit-learn
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: small-changes
...

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-09 21:35:09 +00:00
shamoon
e3fa3fe818 Chore: use rxjs instead of JS setInterval for timers (#8461) 2024-12-09 13:14:48 -08:00
shamoon
0a7c296194 Chore: refactor loading stuff to be more DRY 2024-12-09 12:57:58 -08:00
shamoon
c2e34b36ce Fix: only adjust icon position inside buttons 2024-12-09 12:17:27 -08:00
shamoon
084f2f2822 Enhancement: slightly nicer filter editor loading 2024-12-09 09:52:36 -08:00
shamoon
e4f69dc945 Feature: bulk edit custom field values (#8428) 2024-12-09 17:35:49 +00:00
shamoon
8574d28c6f Fix: hide other filtering buttons, fix e2e test paths 2024-12-08 21:23:41 -08:00
shamoon
27f575c2d1 Fix some strings 2024-12-08 20:34:16 -08:00
Quadrubo
8e3a021b0f Documentation: correct socket info for gpg decryption of emails (#8453) 2024-12-08 16:27:11 -08:00
shamoon
20e45407f5 Fix: fix trash column display on mobile 2024-12-07 08:05:11 -08:00
shamoon
f836c5ce3e Chore: some logging to trash emptying 2024-12-07 08:04:10 -08:00
dependabot[bot]
476d753720 Chore(deps): Bump django from 5.1.3 to 5.1.4 (#8445)
Bumps [django](https://github.com/django/django) from 5.1.3 to 5.1.4.
- [Commits](https://github.com/django/django/compare/5.1.3...5.1.4)

---
updated-dependencies:
- dependency-name: django
  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-12-06 16:20:38 -08:00
shamoon
65b48952cd Refactor: loading component 2024-12-06 00:46:21 -08:00
shamoon
0647812699 Enhancement: improved loading visuals (#8435) 2024-12-05 20:26:28 -08:00
shamoon
8722ff481c Fix: use update preview component for dashboard widget 2024-12-04 23:14:08 -08:00
shamoon
0c883d064e Fix: some random frontend fixes and coverage 2024-12-04 21:15:45 -08:00
shamoon
df2c139721 Chore: mark another test flaky 2024-12-04 12:10:31 -08:00
shamoon
24b678b7e6 Delete workflow-dialog.component.scss 2024-12-04 00:21:28 -08:00
Paul-Vincent Roll
1f85da64e4 Documentation: add search caveat when using MariaDB (#8424)
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-12-03 13:16:43 -08:00
shamoon
a14b9127b6 Fix: sidebar saved views label, add spacing to settings columns 2024-12-03 11:44:44 -08:00
shamoon
7d182ab894 Enhancement: prune audit logs and management command (#8416) 2024-12-03 19:28:27 +00:00
shamoon
51c339d1b7 Chore: compact settings 2024-12-03 11:19:49 -08:00
shamoon
ab548e36c7 Change: make saved views manage its own component (#8423) 2024-12-03 11:09:02 -08:00
shamoon
ae4e8808b0 Fix: move filter to header, mobile layout improvement 2024-12-03 11:07:04 -08:00
shamoon
b4e369b556 Enhancement: file task filtering (#8421) 2024-12-03 10:44:52 -08:00
shamoon
5f7c60d9a1 Chore: remove some tsconfig and jest stuff 2024-12-03 09:12:38 -08:00
shamoon
fca10227bc Update translation strings 2024-12-02 23:04:58 -08:00
shamoon
007e5a5473 Fix: dont link related document if document is in the trash 2024-12-02 19:42:54 -08:00
shamoon
70d9a6fd36 Enhancement: auto-link duplicate document for failed tasks (#8415) 2024-12-02 19:11:40 -08:00
lufi
0406fca59b Enhancement: include current filename placeholder in workflows (#8319)
Co-authored-by: Trenton H <797416+stumpylog@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2024-12-03 03:09:27 +00:00
shamoon
1d65628132 Feature: email, webhook workflow actions (#8108) 2024-12-03 00:12:40 +00:00
dependabot[bot]
81a5baa451 Chore(deps-dev): Bump the development group with 4 updates (#8414)
* 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.8.0 to 0.8.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/0.8.0...0.8.1)

Updates `pytest` from 8.3.3 to 8.3.4
- [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.3.3...8.3.4)

Updates `pytest-httpx` from 0.34.0 to 0.35.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.34.0...v0.35.0)

Updates `mkdocs-material` from 9.5.46 to 9.5.47
- [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.46...9.5.47)

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

* Update .pre-commit-config.yaml

---------

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-12-02 23:29:56 +00:00
dependabot[bot]
e9254d4eef Chore(deps): Bump codecov/codecov-action in the actions group (#8401)
Bumps the actions group with 1 update: [codecov/codecov-action](https://github.com/codecov/codecov-action).


Updates `codecov/codecov-action` from 4 to 5
- [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/v4...v5)

---
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-12-02 08:59:45 -08:00
shamoon
0fc1860d4c Enhancement: use stable unique IDs for custom field select options (#8299) 2024-12-02 04:15:38 +00:00
dependabot[bot]
00485138f9 Chore(deps-dev): Bump the development group with 4 updates (#8352)
Bumps the development group with 4 updates: [ruff](https://github.com/astral-sh/ruff), [pytest-httpx](https://github.com/Colin-b/pytest_httpx), [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.7.3 to 0.8.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/0.7.3...0.8.0)

Updates `pytest-httpx` from 0.33.0 to 0.34.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.33.0...v0.34.0)

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

Updates `mkdocs-material` from 9.5.44 to 9.5.46
- [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.44...9.5.46)

---
updated-dependencies:
- dependency-name: ruff
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development
- dependency-name: pytest-httpx
  dependency-type: direct:development
  update-type: version-update:semver-minor
  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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-01 18:56:54 -08:00
shamoon
79345f0a69 Enhancement: better TIFF display support (#8087) 2024-12-01 11:46:19 -08:00
dependabot[bot]
3aec0b3372 Chore(deps): Bump the small-changes group across 1 directory with 7 updates (#8399)
Bumps the small-changes group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [django-allauth](https://github.com/sponsors/pennersr) | `65.2.0` | `65.3.0` |
| [channels](https://github.com/django/channels) | `4.1.0` | `4.2.0` |
| [channels-redis](https://github.com/django/channels_redis) | `4.2.0` | `4.2.1` |
| [mysqlclient](https://github.com/PyMySQL/mysqlclient) | `2.2.5` | `2.2.6` |
| [ocrmypdf](https://github.com/ocrmypdf/OCRmyPDF) | `16.6.1` | `16.6.2` |
| [setproctitle](https://github.com/dvarrazzo/py-setproctitle) | `1.3.3` | `1.3.4` |
| [tqdm](https://github.com/tqdm/tqdm) | `4.67.0` | `4.67.1` |



Updates `django-allauth` from 65.2.0 to 65.3.0
- [Commits](https://github.com/sponsors/pennersr/commits)

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

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

Updates `mysqlclient` from 2.2.5 to 2.2.6
- [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.5...v2.2.6)

Updates `ocrmypdf` from 16.6.1 to 16.6.2
- [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/v16.6.1...v16.6.2)

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

Updates `tqdm` from 4.67.0 to 4.67.1
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.67.0...v4.67.1)

---
updated-dependencies:
- dependency-name: django-allauth
  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: channels-redis
  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: ocrmypdf
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: small-changes
- dependency-name: setproctitle
  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
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-01 19:12:23 +00:00
shamoon
dbccd13915 Enhancement: History (audit log) for bulk edit operations (#8196) 2024-12-01 17:53:52 +00:00
Johan Ohly
fbeaed930e Documentation: fix docker hub image name (#8398) 2024-12-01 07:38:49 -08:00
shamoon
5a74a92b74 Fix: missing import in frontend tests 2024-11-29 23:01:15 -08:00
shamoon
d1a4c1f2eb Enhancement: larger previews in action dialogs (#8387) 2024-11-29 22:58:59 -08:00
shamoon
9614528033 Enhancement: filterable list count sorting and opacification (#8386) 2024-11-29 22:36:40 -08:00
shamoon
548a7f05d8 Enhancement: preview button for document list and trash, refactor (#8384) 2024-11-29 21:24:33 -08:00
shamoon
771b6606e0 Fix: include subpath in logo url (#8374) 2024-11-28 08:57:44 -08:00
shamoon
c08ca6e3e5 Fixhancement: dispatch change event from current field prior to save (#8369) 2024-11-27 23:25:52 -08:00
shamoon
a186527f07 Enhancement: use theme-color meta tag (#8359) 2024-11-26 10:24:48 -08:00
shamoon
ecb300845c Update frontend translation strings 2024-11-26 09:23:26 -08:00
shamoon
fcf532f13e Documentation: documentation updates 2024-11-24 14:20:20 -08:00
shamoon
cc93bc41df Chore: tiny typo in ci.yml 2024-11-24 12:55:39 -08:00
shamoon
c6fdf4409b Fix: include distinct on workflow objects filter 2024-11-24 12:22:48 -08:00
shamoon
37f8a77516 Chore: cleanup urls, use actions for some views (#8346) 2024-11-24 12:06:31 -08:00
shamoon
2b29233a1e Feature: scheduled workflow trigger (#8036) 2024-11-24 18:22:31 +00:00
Trenton H
d5572137de Fixes install script to handle languages with dashes or underscores (#8341) 2024-11-23 08:15:13 -08:00
dependabot[bot]
d370704286 Chore(deps): Bump tornado from 6.4.1 to 6.4.2 (#8336)
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2.
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 14:48:51 -08:00
shamoon
be684c9a11 Fix: handle very old dates with positive offset too (#8335) 2024-11-22 08:39:51 -08:00
shamoon
447b4cdb98 Refactor: fix unnecessary use of filterable dropdown sorting (#8328) 2024-11-21 18:50:52 -08:00
shamoon
f6cc2f9fc3 Enhancement: offer link to restored document (#8321) 2024-11-20 16:51:00 -08:00
shamoon
beb69ae01e Update frontend translation strings 2024-11-20 16:42:45 -08:00
shamoon
8bfe68743d Enhancement: support owner permissions for file tasks (#8195) 2024-11-20 20:25:53 +00:00
shamoon
9c1561adfb Change: change update content to handle archive disabled (#8315) 2024-11-20 20:01:13 +00:00
dependabot[bot]
d7d3fed833 Chore(deps): Bump watchdog in the major-versions group (#8257)
Bumps the major-versions group with 1 update: [watchdog](https://github.com/gorakhargosh/watchdog).


Updates `watchdog` from 5.0.3 to 6.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/v5.0.3...v6.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-11-19 08:50:21 -08:00
Kevin Doren
827121808a Enhancement: Add --compare-json option to document_exporter to write json files only if changed (#8261) 2024-11-19 07:20:24 -08:00
shamoon
f0e71330ac Enhancement: next / previous shortcuts for document list (#8309) 2024-11-18 11:05:00 -08:00
shamoon
e4578b4589 Chore: change doc detail icon 2024-11-18 11:03:56 -08:00
shamoon
e94a92ed59 Feature: two-factor authentication (#8012) 2024-11-18 18:34:46 +00:00
ftibi93
6c3d6d562d Documentation: fix docker-compose.portainer.yml GID (#8273) 2024-11-16 04:19:18 +00:00
shamoon
c324a71e91 Fix: include db_index caveat in squashed migrations (#8292) 2024-11-15 07:42:10 -08:00
shamoon
2e6f85cc3a Tweak: use fixed position for navbar (#8279) 2024-11-14 00:22:26 -08:00
shamoon
56ef263540 Fix: fix log line trackby function 2024-11-14 00:05:38 -08:00
shamoon
36ecb8587d Fix: prevent duplicate workflow runs (#8268) 2024-11-13 07:18:21 -08:00
shamoon
ad9bdaf1b8 Merge branch 'main' into dev 2024-11-12 22:35:42 -08:00
shamoon
22f29b3659 Chore: add note about select options to edit dialog (#8267) 2024-11-12 22:25:05 -08:00
shamoon
0dc47d156f Update frontend translation strings 2024-11-12 19:42:39 -08:00
shamoon
3633fb382f Enhancement: save & next / close shortcut key (#8243) 2024-11-12 16:21:10 -08:00
shamoon
a283a65813 Feature: loading preview, better text popup preview (#8011) 2024-11-12 16:20:52 -08:00
Sebastian Steinbeißer
74d0c9fda5 Switch src/documents/bulk*.py from os.path to pathlib.Path (#7862)
Also:
* Ensure that the ruff PTH check remains enabled for these files and
all files added in the future.
* Add some type annotations.
2024-11-12 17:04:07 +00:00
Trenton H
d1f255a22e Chore: Bulk backend dependency updates (#8212) 2024-11-11 11:54:51 -08:00
github-actions[bot]
ceb834bdef Documentation: Add v2.13.5 changelog (#8248)
* Changelog v2.13.5 - 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-11-10 09:03:05 -08:00
shamoon
6c230f098a Bump version to 2.13.5 2024-11-10 08:25:10 -08:00
shamoon
da40d03be6 Merge branch 'dev' 2024-11-10 08:24:31 -08:00
shamoon
a6f4c75a72 Fix: handle page count exception for pw-protected files (#8240) 2024-11-10 03:33:47 -08:00
shamoon
c22a80abd3 Fix: correctly track task id in list for change detection (#8230) 2024-11-08 00:19:05 -08:00
Trenton H
2806b1820e Updates all runner images to use Ubuntu Noble (#8213) 2024-11-08 02:14:47 +00:00
Trenton H
177cc9d985 Fixes admin pages to show trashed documents too (#8068) 2024-11-07 23:04:29 +00:00
shamoon
da85b05ea4 Fix: tag colors stay fixed in list when selected (#8225) 2024-11-07 11:59:03 -08:00
shamoon
82fd706dca Fix: another Firefox plaintext popup fix 2024-11-06 08:58:15 -08:00
shamoon
2f06680f76 Fix: fix re-activation of save button when adding / removing array items (#8208) 2024-11-05 20:01:36 -08:00
shamoon
64095a710a Fix: Dont check empty ASNs 2024-11-05 20:01:36 -08:00
shamoon
005150c84f Fix: fix thumbnail clipping, select inverted color in safari (#8193) 2024-11-04 13:44:35 -08:00
shamoon
c57cd7e298 Fix: select checkbox should remain visible (#8185) 2024-11-04 04:32:57 -08:00
shamoon
ef329fc687 Fix: warn with proper error on ASN exists in trash (#8176) 2024-11-03 17:52:59 -08:00
dependabot[bot]
695967cbb2 Chore(deps): Bump stumpylog/image-cleaner-action in the actions group (#8142)
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.8.0 to 0.9.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.8.0...v0.9.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-11-03 15:33:12 +00:00
github-actions[bot]
c028910cdd Changelog v2.13.4 - GHA (#8170)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-03 06:27:10 -08:00
shamoon
37f836db2c Merge branch 'dev' 2024-11-03 05:28:57 -08:00
shamoon
d01192b81e Fix: fix dark mode icon blend mode in 2.13.3 (#8166) 2024-11-03 05:27:20 -08:00
shamoon
9655c89b69 Bump version to 2.13.4 2024-11-03 05:08:08 -08:00
shamoon
ccf1430f82 Merge branch 'dev' 2024-11-03 05:07:43 -08:00
shamoon
c2509ea439 Fix: fix clipped popup preview in 2.13.3 (#8165) 2024-11-03 05:06:39 -08:00
github-actions[bot]
3cd9bcebe6 Changelog v2.13.3 - GHA (#8160)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-11-02 22:47:15 -07:00
shamoon
2368fd15cb Bump version to 2.13.3 2024-11-02 22:19:03 -07:00
shamoon
cc35b321c5 Merge branch 'dev' 2024-11-02 22:18:35 -07:00
github-actions[bot]
1ddbc31c59 New Crowdin translations by GitHub Action (#8159)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-11-02 21:58:06 -07:00
github-actions[bot]
6ae8ab4af3 New Crowdin translations by GitHub Action (#8130)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-11-02 21:56:35 -07:00
shamoon
37dc791301 Fix: fix auto-clean PDFs, create parent dir for storing unmodified original (#8157) 2024-11-02 20:54:28 -07:00
Yichi Yang
5fed311ffc Fix: correctly handle "exists, false" in custom field query filter (#8158) 2024-11-02 18:40:50 -07:00
dependabot[bot]
e74f182662 Chore(deps-dev): Bump the frontend-eslint-dependencies group (#8145)
Bumps the frontend-eslint-dependencies group in /src-ui with 4 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils) and [eslint](https://github.com/eslint/eslint).


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

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

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

Updates `eslint` from 9.11.1 to 9.14.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.11.1...v9.14.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: "@typescript-eslint/utils"
  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-11-02 02:44:00 +00:00
dependabot[bot]
3f094c88bd Chore(deps-dev): Bump @types/node from 22.7.4 to 22.8.6 in /src-ui (#8148)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.7.4 to 22.8.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-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-01 23:44:28 +00:00
dependabot[bot]
631eaa2109 Chore(deps-dev): Bump @playwright/test from 1.47.2 to 1.48.2 in /src-ui (#8147)
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.47.2 to 1.48.2.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.47.2...v1.48.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>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-01 23:35:04 +00:00
dependabot[bot]
27fadf6963 Chore(deps): Bump uuid from 10.0.0 to 11.0.2 in /src-ui (#8146)
Bumps [uuid](https://github.com/uuidjs/uuid) from 10.0.0 to 11.0.2.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v10.0.0...v11.0.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-01 23:25:07 +00:00
dependabot[bot]
d53b300a7f Chore(deps): Bump tslib from 2.7.0 to 2.8.1 in /src-ui (#8149)
Bumps [tslib](https://github.com/Microsoft/tslib) from 2.7.0 to 2.8.1.
- [Release notes](https://github.com/Microsoft/tslib/releases)
- [Commits](https://github.com/Microsoft/tslib/compare/v2.7.0...v2.8.1)

---
updated-dependencies:
- dependency-name: tslib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-01 23:13:13 +00:00
dependabot[bot]
6cc626fd86 Chore(deps-dev): Bump @codecov/webpack-plugin in /src-ui (#8150)
Bumps @codecov/webpack-plugin from 1.2.0 to 1.2.1.

---
updated-dependencies:
- dependency-name: "@codecov/webpack-plugin"
  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-11-01 23:01:54 +00:00
dependabot[bot]
8b8f1af513 Chore(deps-dev): Bump @types/jest (#8144)
Bumps the frontend-jest-dependencies group in /src-ui with 1 update: [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest).


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

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-jest-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-01 22:45:03 +00:00
dependabot[bot]
4226a1bddc Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 21 updates (#8143)
* Chore(deps): Bump the frontend-angular-dependencies group

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

| Package | From | To |
| --- | --- | --- |
| [@angular/cdk](https://github.com/angular/components) | `18.2.6` | `18.2.11` |
| [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `18.2.6` | `18.2.10` |
| [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `18.2.6` | `18.2.10` |
| [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `18.2.6` | `18.2.10` |
| [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `18.2.6` | `18.2.10` |
| [@angular/localize](https://github.com/angular/angular) | `18.2.6` | `18.2.10` |
| [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `18.2.6` | `18.2.10` |
| [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `18.2.6` | `18.2.10` |
| [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `18.2.6` | `18.2.10` |
| [@ng-select/ng-select](https://github.com/ng-select/ng-select) | `13.9.0` | `13.9.1` |
| [ng2-pdf-viewer](https://github.com/VadimDez/ng2-pdf-viewer) | `10.3.1` | `10.3.4` |
| [@angular-devkit/build-angular](https://github.com/angular/angular-cli) | `18.2.6` | `18.2.11` |
| [@angular-devkit/core](https://github.com/angular/angular-cli) | `18.2.6` | `18.2.11` |
| [@angular-devkit/schematics](https://github.com/angular/angular-cli) | `18.2.6` | `18.2.11` |
| [@angular-eslint/builder](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/builder) | `18.3.1` | `18.4.0` |
| [@angular-eslint/eslint-plugin](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin) | `18.3.1` | `18.4.0` |
| [@angular-eslint/eslint-plugin-template](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/eslint-plugin-template) | `18.3.1` | `18.4.0` |
| [@angular-eslint/schematics](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/schematics) | `18.3.1` | `18.4.0` |
| [@angular-eslint/template-parser](https://github.com/angular-eslint/angular-eslint/tree/HEAD/packages/template-parser) | `18.3.1` | `18.4.0` |
| [@angular/cli](https://github.com/angular/angular-cli) | `18.2.6` | `18.2.11` |
| [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `18.2.6` | `18.2.10` |


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

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

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

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

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

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

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

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

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

Updates `@ng-select/ng-select` from 13.9.0 to 13.9.1
- [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/v13.9.0...v13.9.1)

Updates `ng2-pdf-viewer` from 10.3.1 to 10.3.4
- [Release notes](https://github.com/VadimDez/ng2-pdf-viewer/releases)
- [Changelog](https://github.com/VadimDez/ng2-pdf-viewer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/VadimDez/ng2-pdf-viewer/compare/10.3.1...10.3.4)

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

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

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

Updates `@angular-eslint/builder` from 18.3.1 to 18.4.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/v18.4.0/packages/builder)

Updates `@angular-eslint/eslint-plugin` from 18.3.1 to 18.4.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/v18.4.0/packages/eslint-plugin)

Updates `@angular-eslint/eslint-plugin-template` from 18.3.1 to 18.4.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/v18.4.0/packages/eslint-plugin-template)

Updates `@angular-eslint/schematics` from 18.3.1 to 18.4.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/v18.4.0/packages/schematics)

Updates `@angular-eslint/template-parser` from 18.3.1 to 18.4.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/v18.4.0/packages/template-parser)

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

Updates `@angular/compiler-cli` from 18.2.6 to 18.2.10
- [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/18.2.10/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: "@ng-select/ng-select"
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: ng2-pdf-viewer
  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-devkit/core"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: frontend-angular-dependencies
- dependency-name: "@angular-devkit/schematics"
  dependency-type: direct:development
  update-type: version-update:semver-patch
  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-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>

* Change output name

---------

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-11-01 22:32:12 +00:00
shamoon
289a7a2348 Documentation: re-add 2.13.0 changelog (#8135) 2024-10-31 12:18:32 -07:00
horvatkm
702ab1b77a Chore: update field in bug-report.yml (#8123) 2024-10-30 22:07:35 +00:00
shamoon
e22ccbda26 Fix: dont use filters for inverted thumbnails (#8121) 2024-10-30 11:43:02 -07:00
shamoon
56d296f04b Fix: use static object for activedisplayfields to prevent changes (#8120) 2024-10-30 11:21:01 -07:00
shamoon
11cfa0871e Fix: dont invert pdf colors in FF (#8110) 2024-10-30 00:54:19 -07:00
shamoon
159344f033 Fix: make mail account password and refresh token text fields (#8107) 2024-10-29 21:54:47 -07:00
github-actions[bot]
d8cfed5f5e Changelog v2.13.2 - GHA (#8103)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2024-10-29 12:45:11 -07:00
568 changed files with 124436 additions and 85588 deletions

View File

@@ -1,117 +0,0 @@
# Paperless NGX Development Environment
## Overview
Welcome to the Paperless NGX development environment! This setup uses VSCode DevContainers to provide a consistent and seamless development experience.
### What are DevContainers?
DevContainers are a feature in VSCode that allows you to develop within a Docker container. This ensures that your development environment is consistent across different machines and setups. By defining a containerized environment, you can eliminate the "works on my machine" problem.
### Advantages of DevContainers
- **Consistency**: Same environment for all developers.
- **Isolation**: Separate development environment from your local machine.
- **Reproducibility**: Easily recreate the environment on any machine.
- **Pre-configured Tools**: Include all necessary tools and dependencies in the container.
## DevContainer Setup
The DevContainer configuration provides up all the necessary services for Paperless NGX, including:
- Redis
- Gotenberg
- Tika
Data is stored using Docker volumes to ensure persistence across container restarts.
## Configuration Files
The setup includes debugging configurations (`launch.json`) and tasks (`tasks.json`) to help you manage and debug various parts of the project:
- **Backend Debugging:**
- `manage.py runserver`
- `manage.py document-consumer`
- `celery`
- **Maintenance Tasks:**
- Create superuser
- Run migrations
- Recreate virtual environment (`.venv` with pipenv)
- Compile frontend assets
## Getting Started
### Step 1: Running the DevContainer
To start the DevContainer:
1. Open VSCode.
2. Open the project folder.
3. Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
4. Type and select `Dev Containers: Rebuild and Reopen in Container`.
VSCode will build and start the DevContainer environment.
### Step 2: Initial Setup
Once the DevContainer is up and running, perform the following steps:
1. **Compile Frontend Assets**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Frontend Compile`.
2. **Run Database Migrations**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Migrate Database`.
3. **Create Superuser**:
- Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
- Select `Tasks: Run Task`.
- Choose `Create Superuser`.
### Debugging and Running Services
You can start and debug backend services either as debugging sessions via `launch.json` or as tasks.
#### Using `launch.json`:
1. Press `F5` or go to the **Run and Debug** view in VSCode.
2. Select the desired configuration:
- `Runserver`
- `Document Consumer`
- `Celery`
#### Using Tasks:
1. Open the command palette:
- **Windows/Linux**: `Ctrl+Shift+P`
- **Mac**: `Cmd+Shift+P`
2. Select `Tasks: Run Task`.
3. Choose the desired task:
- `Runserver`
- `Document Consumer`
- `Celery`
### Additional Maintenance Tasks
Additional tasks are available for common maintenance operations:
- **Recreate .venv**: For setting up the virtual environment using pipenv.
- **Migrate Database**: To apply database migrations.
- **Create Superuser**: To create an admin user for the application.
## Let's Get Started!
Follow the steps above to get your development environment up and running. Happy coding!

View File

@@ -3,14 +3,26 @@
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
"service": "paperless-development",
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
"postCreateCommand": "/bin/bash -c pre-commit install && pipenv install --dev",
"postCreateCommand": "pipenv install --dev && pipenv run pre-commit install",
"customizations": {
"vscode": {
"extensions": [
"mhutchie.git-graph",
"ms-python.python"
]
"extensions": [
"mhutchie.git-graph",
"ms-python.python",
"ms-vscode.js-debug-nightly",
"eamodio.gitlens",
"yzhang.markdown-all-in-one"
],
"settings": {
"python.defaultInterpreterPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
"python.pythonPath": "/usr/src/paperless/paperless-ngx/.venv/bin/python",
"python.terminal.activateEnvInCurrentTerminal": true,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
}
}
},
"remoteUser": "paperless"
}
},
"remoteUser": "paperless"
}

View File

@@ -27,7 +27,7 @@ services:
image: docker.io/library/redis:7
restart: unless-stopped
volumes:
- redisdata:/data
- ./redisdata:/data
# No ports need to be exposed; the VSCode DevContainer plugin manages them.
paperless-development:
@@ -43,14 +43,16 @@ services:
volumes:
- ..:/usr/src/paperless/paperless-ngx:delegated
- ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files
- pipenv:/usr/src/paperless/paperless-ngx/.venv # Pipenv environment persisted in volume
- pipenv:/usr/src/paperless/paperless-ngx/.venv
- /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container
- /usr/src/paperless/paperless-ngx/src/.pytest_cache
- /usr/src/paperless/paperless-ngx/.ruff_cache
- /usr/src/paperless/paperless-ngx/htmlcov
- /usr/src/paperless/paperless-ngx/.coverage
- data:/usr/src/paperless/paperless-ngx/data
- media:/usr/src/paperless/paperless-ngx/media
- ./data:/usr/src/paperless/paperless-ngx/data
- ./media:/usr/src/paperless/paperless-ngx/media
- ./consume:/usr/src/paperless/paperless-ngx/consume
- ~/.gitconfig:/usr/src/paperless/.gitconfig:ro
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_TIKA_ENABLED: 1
@@ -78,7 +80,4 @@ services:
restart: unless-stopped
volumes:
data:
media:
redisdata:
pipenv:

View File

@@ -2,42 +2,57 @@
"version": "0.2.0",
"configurations": [
{
"name": "manage.py runserver",
"name": "Chrome: Debug Angular Frontend",
"description": "Debug the Angular Dev Frontend in Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:4200",
"webRoot": "${workspaceFolder}/src-ui",
"preLaunchTask": "Start: Frontend Angular"
},
{
"name": "Debug: Backend Server (manage.py runserver)",
"description": "Debug the Django Backend Server",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/src/manage.py",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["runserver"],
"django": true
},
{
"name": "manage.py document_consumer",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/src/manage.py",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["document_consumer"],
"django": true
},
{
"name": "celery",
"type": "python",
"cwd": "${workspaceFolder}/src",
"request": "launch",
"module": "celery",
"args": [
"runserver"
],
"django": true,
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "${workspaceFolder}/src"
},
},
"python": "${workspaceFolder}/.venv/bin/python"
},
{
"name": "Debug: Consumer Service (manage.py document_consumer)",
"description": "Debug the Consumer Service which processes files from a directory",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/src/manage.py",
"args": [
"-A",
"paperless",
"worker",
"-l",
"DEBUG"
]
"document_consumer"
],
"django": true,
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "${workspaceFolder}/src"
},
"python": "${workspaceFolder}/.venv/bin/python"
}
],
"compounds": [
{
"name": "Debug: FullStack",
"description": "Debug run the Angular dev frontend, Django backend, and consumer service",
"configurations": [
"Chrome: Debug Angular Frontend",
"Debug: Backend Server (manage.py runserver)",
"Debug: Consumer Service (manage.py document_consumer)"
],
"preLaunchTask": "Start: Celery Worker"
}
]
}

View File

@@ -1,27 +1,84 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "manage.py document_consumer",
"type": "shell",
"command": "pipenv run python manage.py document_consumer",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src"
}
{
"label": "Start: Celery Worker",
"description": "Start the Celery Worker which processes background and consume tasks",
"type": "shell",
"command": "pipenv run celery --app paperless worker -l DEBUG",
"isBackground": true,
"options": {
"cwd": "${workspaceFolder}/src"
},
"problemMatcher": [
{
"owner": "custom",
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "celery.*",
"endsPattern": "ready"
}
}
]
},
{
"label": "manage.py runserver",
"label": "Start: Frontend Angular",
"description": "Start the Frontend Angular Dev Server",
"type": "shell",
"command": "npm start",
"isBackground": true,
"options": {
"cwd": "${workspaceFolder}/src-ui"
},
"problemMatcher": [
{
"owner": "custom",
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"activeOnStart": true,
"beginsPattern": ".*",
"endsPattern": "Compiled successfully"
}
}
]
},
{
"label": "Start: Consumer Service (manage.py document_consumer)",
"description": "Start the Consumer Service which processes files from a directory",
"type": "shell",
"command": "pipenv run python manage.py document_consumer",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src"
}
},
{
"label": "Start: Backend Server (manage.py runserver)",
"description": "Start the Backend Server which serves the Django API and the compiled Angular frontend",
"type": "shell",
"command": "pipenv run python manage.py runserver",
"group": "build",
@@ -37,100 +94,130 @@
"options": {
"cwd": "${workspaceFolder}/src"
}
},
{
"label": "Maintenance: manage.py migrate",
"description": "Apply database migrations",
"type": "shell",
"command": "pipenv run python manage.py migrate",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
{
"label": "Maintenance: manage.py migrate",
"type": "shell",
"command": "pipenv run python manage.py migrate",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
"options": {
"cwd": "${workspaceFolder}/src"
}
},
"options": {
"cwd": "${workspaceFolder}/src"
{
"label": "Maintenance: Build Documentation",
"description": "Build the documentation with MkDocs",
"type": "shell",
"command": "pipenv run mkdocs build --config-file mkdocs.yml && pipenv run mkdocs serve",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "Maintenance: manage.py createsuperuser",
"description": "Create a superuser",
"type": "shell",
"command": "pipenv run python manage.py createsuperuser",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src"
}
},
{
"label": "Maintenance: recreate .venv",
"description": "Recreate the python virtual environment and install python dependencies",
"type": "shell",
"command": "rm -R -v .venv/* || pipenv install --dev",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "Maintenance: Install Frontend Dependencies",
"description": "Install frontend (npm) dependencies",
"type": "npm",
"script": "install",
"path": "src-ui",
"group": "clean",
"problemMatcher": [],
"detail": "install dependencies from package"
},
{
"description": "Clean install frontend dependencies and build the frontend for production",
"label": "Maintenance: Compile frontend for production",
"type": "shell",
"command": "npm ci && ./node_modules/.bin/ng build --configuration production",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src-ui"
}
},
{
"label": "Project Setup: Run all Init Tasks",
"description": "Runs all init tasks to setup the project including migrate the database, create a superuser and compile the frontend for production",
"dependsOrder": "sequence",
"dependsOn": [
"Maintenance: manage.py migrate",
"Maintenance: manage.py createsuperuser",
"Maintenance: Compile frontend for production"
]
},
{
"label": "Project Start: Run all Services",
"description": "Runs all services required to start the project including the Celery Worker, the Consumer Service and the Backend Server",
"dependsOn": [
"Start: Celery Worker",
"Start: Consumer Service (manage.py document_consumer)",
"Start: Backend Server (manage.py runserver)"
]
}
},
{
"label": "Maintenance: manage.py createsuperuser",
"type": "shell",
"command": "pipenv run python manage.py createsuperuser",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src"
}
},
{
"label": "compile frontend",
"type": "shell",
"command": "npm ci && ./node_modules/.bin/ng build --configuration production",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src-ui"
}
},
{
"label": "Maintenance: recreate .venv",
"type": "shell",
"command": "rm -R -v .venv/* || pipenv install --dev",
"group": "none",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "Celery Worker",
"type": "shell",
"command": "pipenv run celery --app paperless worker -l DEBUG",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "shared",
"showReuseMessage": false,
"clear": true,
"revealProblems": "onProblem"
},
"options": {
"cwd": "${workspaceFolder}/src"
}
}
]
}
}

View File

@@ -98,7 +98,7 @@ body:
label: Browser
description: Which browser you are using, if relevant.
placeholder: e.g. Chrome, Safari
- type: input
- type: textarea
id: config-changes
attributes:
label: Configuration changes

View File

@@ -2,10 +2,10 @@ blank_issues_enabled: false
contact_links:
- name: 🤔 Questions and Help
url: https://github.com/paperless-ngx/paperless-ngx/discussions
about: This issue tracker is not for support questions. Please refer to our Discussions.
about: General questions or support for using Paperless-ngx.
- name: 💬 Chat
url: https://matrix.to/#/#paperlessngx:matrix.org
about: Want to discuss Paperless-ngx with others? Check out our chat.
- name: 🚀 Feature Request
url: https://github.com/paperless-ngx/paperless-ngx/discussions/new?category=feature-requests
about: Remember to search for existing feature requests and "up-vote" any you like
about: Remember to search for existing feature requests and "up-vote" those that you like.

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: "2024.0.3"
DEFAULT_PIP_ENV_VERSION: "2024.4.1"
# This is the default version of Python to use in most steps which aren't specific
DEFAULT_PYTHON_VERSION: "3.11"
@@ -30,7 +30,7 @@ jobs:
github.repository
name: Linting Checks
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
-
name: Checkout repository
@@ -46,7 +46,7 @@ jobs:
documentation:
name: "Build & Deploy Documentation"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- pre-commit
steps:
@@ -95,7 +95,7 @@ jobs:
tests-backend:
name: "Backend Tests (Python ${{ matrix.python-version }})"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- pre-commit
strategy:
@@ -170,7 +170,7 @@ jobs:
install-frontend-depedendencies:
name: "Install Frontend Dependencies"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- pre-commit
steps:
@@ -201,7 +201,7 @@ jobs:
tests-frontend:
name: "Frontend Tests (Node ${{ matrix.node-version }} - ${{ matrix.shard-index }}/${{ matrix.shard-count }})"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- install-frontend-depedendencies
strategy:
@@ -261,7 +261,7 @@ jobs:
tests-coverage-upload:
name: "Upload to Codecov"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- tests-backend
- tests-frontend
@@ -283,7 +283,7 @@ jobs:
merge-multiple: true
-
name: Upload frontend coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
# not required for public repos, but intermittently fails otherwise
token: ${{ secrets.CODECOV_TOKEN }}
@@ -299,7 +299,7 @@ jobs:
path: src/
-
name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
# not required for public repos, but intermittently fails otherwise
token: ${{ secrets.CODECOV_TOKEN }}
@@ -333,7 +333,7 @@ jobs:
build-docker-image:
name: Build Docker image for ${{ github.ref_name }}
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
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 }}
@@ -406,7 +406,7 @@ jobs:
-
name: Login to Docker Hub
uses: docker/login-action@v3
# Don't attempt to login is not pushing to Docker Hub
# Don't attempt to login if not pushing to Docker Hub
if: steps.push-other-places.outputs.enable == 'true'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -414,7 +414,7 @@ jobs:
-
name: Login to Quay.io
uses: docker/login-action@v3
# Don't attempt to login is not pushing to Quay.io
# Don't attempt to login if not pushing to Quay.io
if: steps.push-other-places.outputs.enable == 'true'
with:
registry: quay.io
@@ -461,7 +461,7 @@ jobs:
needs:
- build-docker-image
- documentation
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
-
name: Checkout
@@ -569,7 +569,7 @@ jobs:
publish-release:
name: "Publish Release"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
outputs:
prerelease: ${{ steps.get_version.outputs.prerelease }}
changelog: ${{ steps.create-release.outputs.body }}
@@ -619,7 +619,7 @@ jobs:
append-changelog:
name: "Append Changelog"
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- publish-release
if: needs.publish-release.outputs.prerelease == 'false'

View File

@@ -21,7 +21,7 @@ jobs:
cleanup-images:
name: Cleanup Image Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
@@ -33,7 +33,7 @@ jobs:
-
name: Clean temporary images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/ephemeral@v0.8.0
uses: stumpylog/image-cleaner-action/ephemeral@v0.9.0
with:
token: "${{ env.TOKEN }}"
owner: "${{ github.repository_owner }}"
@@ -47,7 +47,7 @@ jobs:
cleanup-untagged-images:
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs:
- cleanup-images
strategy:
@@ -61,7 +61,7 @@ jobs:
-
name: Clean untagged images
if: "${{ env.TOKEN != '' }}"
uses: stumpylog/image-cleaner-action/untagged@v0.8.0
uses: stumpylog/image-cleaner-action/untagged@v0.9.0
with:
token: "${{ env.TOKEN }}"
owner: "${{ github.repository_owner }}"

View File

@@ -23,7 +23,7 @@ on:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
actions: read
contents: read

View File

@@ -16,7 +16,7 @@ jobs:
synchronize-with-crowdin:
name: Crowdin Sync
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout

View File

@@ -15,7 +15,7 @@ permissions:
jobs:
pr_opened_or_reopened:
name: pr_opened_or_reopened
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
permissions:
# write permission is required for autolabeler
pull-requests: write

View File

@@ -17,7 +17,7 @@ jobs:
stale:
name: 'Stale'
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/stale@v9
with:
@@ -33,7 +33,7 @@ jobs:
lock-threads:
name: 'Lock Old Threads'
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: dessant/lock-threads@v5
with:
@@ -59,7 +59,7 @@ jobs:
close-answered-discussions:
name: 'Close Answered Discussions'
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v7
with:
@@ -116,7 +116,7 @@ jobs:
close-outdated-discussions:
name: 'Close Outdated Discussions'
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v7
with:
@@ -208,7 +208,7 @@ jobs:
close-unsupported-feature-requests:
name: 'Close Unsupported Feature Requests'
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v7
with:

6
.gitignore vendored
View File

@@ -100,3 +100,9 @@ scripts/nuke
# celery schedule file
celerybeat-schedule*
# ignore .devcontainer sub folders
/.devcontainer/consume/
/.devcontainer/data/
/.devcontainer/media/
/.devcontainer/redisdata/

View File

@@ -5,7 +5,7 @@
repos:
# General hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: check-docstring-first
- id: check-json
@@ -29,7 +29,7 @@ repos:
- id: check-case-conflict
- id: detect-private-key
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
rev: v2.4.0
hooks:
- id: codespell
exclude: "(^src-ui/src/locale/)|(^src-ui/e2e/)|(^src/paperless_mail/tests/samples/)"
@@ -46,9 +46,12 @@ repos:
- ts
- markdown
exclude: "(^Pipfile\\.lock$)"
additional_dependencies:
- prettier@3.3.3
- 'prettier-plugin-organize-imports@4.1.0'
# Python hooks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.6.8'
rev: v0.9.2
hooks:
- id: ruff
- id: ruff-format

View File

@@ -1,16 +0,0 @@
{
# https://prettier.io/docs/en/options.html#semicolons
"semi": false,
# https://prettier.io/docs/en/options.html#quotes
"singleQuote": true,
# https://prettier.io/docs/en/options.html#trailing-commas
"trailingComma": "es5",
"overrides": [
{
"files": ["docs/*.md"],
"options": {
"tabWidth": 4,
}
}
]
}

19
.prettierrc.js Normal file
View File

@@ -0,0 +1,19 @@
const config = {
// https://prettier.io/docs/en/options.html#semicolons
semi: false,
// https://prettier.io/docs/en/options.html#quotes
singleQuote: true,
// https://prettier.io/docs/en/options.html#trailing-commas
trailingComma: 'es5',
overrides: [
{
files: ['docs/*.md'],
options: {
tabWidth: 4,
},
},
],
plugins: [require('prettier-plugin-organize-imports')],
}
module.exports = config

View File

@@ -31,17 +31,55 @@ extend-select = [
"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
"PTH", # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
]
# 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", "T201"]
"src/documents/consumer.py" = ["PTH"] # TODO Enable & remove
"src/documents/file_handling.py" = ["PTH"] # TODO Enable & remove
"src/documents/management/commands/document_consumer.py" = ["PTH"] # TODO Enable & remove
"src/documents/management/commands/document_exporter.py" = ["PTH"] # TODO Enable & remove
"src/documents/migrations/0012_auto_20160305_0040.py" = ["PTH"] # TODO Enable & remove
"src/documents/migrations/0014_document_checksum.py" = ["PTH"] # TODO Enable & remove
"src/documents/migrations/1003_mime_types.py" = ["PTH"] # TODO Enable & remove
"src/documents/migrations/1012_fix_archive_files.py" = ["PTH"] # TODO Enable & remove
"src/documents/models.py" = ["SIM115", "PTH"] # TODO PTH Enable & remove
"src/documents/parsers.py" = ["PTH"] # TODO Enable & remove
"src/documents/signals/handlers.py" = ["PTH"] # TODO Enable & remove
"src/documents/tasks.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_api_app_config.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_api_bulk_download.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_api_documents.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_classifier.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_consumer.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_file_handling.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_management.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_management_consumer.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_management_exporter.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_management_thumbnails.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_migration_archive_files.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_migration_document_pages_count.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_migration_mime_type.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_sanity_check.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_tasks.py" = ["PTH"] # TODO Enable & remove
"src/documents/tests/test_views.py" = ["PTH"] # TODO Enable & remove
"src/documents/views.py" = ["PTH"] # TODO Enable & remove
"src/paperless/checks.py" = ["PTH"] # TODO Enable & remove
"src/paperless/settings.py" = ["PTH"] # TODO Enable & remove
"src/paperless/tests/test_checks.py" = ["PTH"] # TODO Enable & remove
"src/paperless/urls.py" = ["PTH"] # TODO Enable & remove
"src/paperless/views.py" = ["PTH"] # TODO Enable & remove
"src/paperless_mail/mail.py" = ["PTH"] # TODO Enable & remove
"src/paperless_mail/preprocessor.py" = ["PTH"] # TODO Enable & remove
"src/paperless_tesseract/parsers.py" = ["PTH"] # TODO Enable & remove
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001", "PTH"] # TODO PTH Enable & remove
"src/paperless_tika/tests/test_live_tika.py" = ["PTH"] # TODO Enable & remove
"src/paperless_tika/tests/test_tika_parser.py" = ["PTH"] # TODO Enable & remove
"*/tests/*.py" = ["E501", "SIM117"]
"*/migrations/*.py" = ["E501", "SIM", "T201"]
"src/paperless_tesseract/tests/test_parser.py" = ["RUF001"]
"src/documents/models.py" = ["SIM115"]
[lint.isort]
force-single-line = true

View File

@@ -39,7 +39,7 @@ COPY Pipfile* ./
RUN set -eux \
&& echo "Installing pipenv" \
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2024.0.3 \
&& python3 -m pip install --no-cache-dir --upgrade pipenv==2024.4.1 \
&& echo "Generating requirement.txt" \
&& pipenv requirements > requirements.txt
@@ -233,11 +233,11 @@ RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
&& python3 -m pip install --no-cache-dir --upgrade wheel \
&& echo "Installing Python requirements" \
&& curl --fail --silent --show-error --location \
--output psycopg_c-3.2.2-cp312-cp312-linux_x86_64.whl \
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.2/psycopg_c-3.2.2-cp312-cp312-linux_x86_64.whl \
--output psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl \
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_x86_64.whl \
&& curl --fail --silent --show-error --location \
--output psycopg_c-3.2.2-cp312-cp312-linux_aarch64.whl \
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.2/psycopg_c-3.2.2-cp312-cp312-linux_aarch64.whl \
--output psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl \
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.4/psycopg_c-3.2.4-cp312-cp312-linux_aarch64.whl \
&& python3 -m pip install --default-timeout=1000 --find-links . --requirement requirements.txt \
&& echo "Installing NLTK data" \
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \

14
Pipfile
View File

@@ -7,8 +7,8 @@ name = "pypi"
dateparser = "~=1.2"
# WARNING: django does not use semver.
# Only patch versions are guaranteed to not introduce breaking changes.
django = "~=5.1.1"
django-allauth = {extras = ["socialaccount"], version = "*"}
django = "~=5.1.5"
django-allauth = {extras = ["mfa", "socialaccount"], version = "*"}
django-auditlog = "*"
django-celery-results = "*"
django-compression-middleware = "*"
@@ -18,12 +18,12 @@ django-filter = "~=24.3"
django-guardian = "*"
django-multiselectfield = "*"
django-soft-delete = "*"
djangorestframework = "==3.15.2"
djangorestframework = "~=3.15.2"
djangorestframework-guardian = "*"
drf-writable-nested = "*"
bleach = "*"
celery = {extras = ["redis"], version = "*"}
channels = "~=4.1"
channels = "~=4.2"
channels-redis = "*"
concurrent-log-handler = "*"
filelock = "*"
@@ -37,7 +37,7 @@ jinja2 = "~=3.1"
langdetect = "*"
mysqlclient = "*"
nltk = "*"
ocrmypdf = "~=16.5"
ocrmypdf = "~=16.8"
pathvalidate = "*"
pdf2image = "*"
psycopg = {version = "*", extras = ["c"]}
@@ -49,13 +49,13 @@ python-magic = "*"
pyzbar = "*"
rapidfuzz = "*"
redis = {extras = ["hiredis"], version = "*"}
scikit-learn = "~=1.5"
scikit-learn = "~=1.6"
setproctitle = "*"
tika-client = "*"
tqdm = "*"
# See https://github.com/paperless-ngx/paperless-ngx/issues/5494
uvicorn = {extras = ["standard"], version = "==0.25.0"}
watchdog = "~=4.0"
watchdog = "~=6.0"
whitenoise = "~=6.8"
whoosh = "~=2.7"
zxing-cpp = {version = "*", platform_machine = "== 'x86_64'"}

4669
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@ A full list of [features](https://docs.paperless-ngx.com/#features) and [screens
# Getting started
The easiest way to deploy paperless is `docker compose`. The files in the [`/docker/compose` directory](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose) are configured to pull the image from GitHub Packages.
The easiest way to deploy paperless is `docker compose`. The files in the [`/docker/compose` directory](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose) are configured to pull the image from the GitHub container registry.
If you'd like to jump right in, you can configure a `docker compose` environment with our install script:

View File

@@ -1,26 +1,17 @@
###############################################################################
# Paperless-ngx settings #
###############################################################################
# See http://docs.paperless-ngx.com/configuration/ for all available options.
# The UID and GID of the user used to run paperless in the container. Set this
# to your UID and GID on the host so that you have write access to the
# consumption directory.
#USERMAP_UID=1000
#USERMAP_GID=1000
# Additional languages to install for text recognition, separated by a
# whitespace. Note that this is
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
# language used for OCR.
# The container installs English, German, Italian, Spanish and French by
# default.
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
# for available languages.
#PAPERLESS_OCR_LANGUAGES=tur ces
###############################################################################
# Paperless-specific settings #
###############################################################################
# All settings defined in the paperless.conf.example can be used here. The
# Docker setup does not use the configuration file.
# A few commonly adjusted settings are provided below.
# See the documentation linked above for all options. A few commonly adjusted settings
# are provided below.
# This is required if you will be exposing Paperless-ngx on a public domain
# (if doing so please consider security measures such as reverse proxy)
@@ -30,13 +21,17 @@
# be a very long sequence of random characters. You don't need to remember it.
#PAPERLESS_SECRET_KEY=change-me
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
# Use this variable to set a timezone for the Paperless Docker containers. Defaults to UTC.
#PAPERLESS_TIME_ZONE=America/Los_Angeles
# The default language to use for OCR. Set this to the language most of your
# documents are written in.
#PAPERLESS_OCR_LANGUAGE=eng
# Set if accessing paperless via a domain subpath e.g. https://domain.com/PATHPREFIX and using a reverse-proxy like traefik or nginx
#PAPERLESS_FORCE_SCRIPT_NAME=/PATHPREFIX
#PAPERLESS_STATIC_URL=/PATHPREFIX/static/ # trailing slash required
# Additional languages to install for text recognition, separated by a whitespace.
# Note that this is different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines
# the language used for OCR.
# The container installs English, German, Italian, Spanish and French by default.
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
# for available languages.
#PAPERLESS_OCR_LANGUAGES=tur ces

View File

@@ -19,6 +19,8 @@
#
# - Open portainer Stacks list and click 'Add stack'
# - Paste the contents of this file and assign a name, e.g. 'paperless'
# - Upload 'docker-compose.env' by clicking on 'Load variables from .env file'
# - Modify the environment variables as needed
# - Click 'Deploy the stack' and wait for it to be deployed
# - Open the list of containers, select paperless_webserver_1
# - Click 'Console' and then 'Connect' to open the command line inside the container
@@ -61,28 +63,8 @@ services:
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
# The UID and GID of the user used to run paperless in the container. Set this
# to your UID and GID on the host so that you have write access to the
# consumption directory.
USERMAP_UID: 1000
USERMAP_GID: 100
# Additional languages to install for text recognition, separated by a
# whitespace. Note that this is
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
# language used for OCR.
# The container installs English, German, Italian, Spanish and French by
# default.
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
# for available languages.
#PAPERLESS_OCR_LANGUAGES: tur ces
# Adjust this key if you plan to make paperless available publicly. It should
# be a very long sequence of random characters. You don't need to remember it.
#PAPERLESS_SECRET_KEY: change-me
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
#PAPERLESS_TIME_ZONE: America/Los_Angeles
# The default language to use for OCR. Set this to the language most of your
# documents are written in.
#PAPERLESS_OCR_LANGUAGE: eng
env_file:
- stack.env
volumes:
data:

View File

@@ -15,7 +15,8 @@ for command in decrypt_documents \
document_sanity_checker \
document_fuzzy_match \
manage_superuser \
convert_mariadb_uuid;
convert_mariadb_uuid \
prune_audit_logs;
do
echo "installing $command..."
sed "s/management_command/$command/g" management_script.sh > /usr/local/bin/$command

View File

@@ -81,8 +81,8 @@ $ docker compose down
1. If you pull the image from the docker hub, all you need to do is:
```shell-session
$ docker compose pull
$ docker compose up
docker compose pull
docker compose up
```
The Docker Compose files refer to the `latest` version, which is
@@ -91,9 +91,9 @@ $ docker compose down
1. If you built the image yourself, do the following:
```shell-session
$ git pull
$ docker compose build
$ docker compose up
git pull
docker compose build
docker compose up
```
Running `docker compose up` will also apply any new database migrations.
@@ -155,7 +155,7 @@ following:
environment before that, if you use one.
```shell-session
$ pip install -r requirements.txt
pip install -r requirements.txt
```
!!! note
@@ -168,8 +168,8 @@ following:
3. Migrate the database.
```shell-session
$ cd src
$ python3 manage.py migrate # (1)
cd src
python3 manage.py migrate # (1)
```
1. Including `sudo -Hu <paperless_user>` may be required
@@ -241,6 +241,7 @@ document_exporter target [-c] [-d] [-f] [-na] [-nt] [-p] [-sm] [-z]
optional arguments:
-c, --compare-checksums
-cj, --compare-json
-d, --delete
-f, --use-filename-format
-na, --no-archive
@@ -269,7 +270,8 @@ only export changed and added files. Paperless determines whether a file
has changed by inspecting the file attributes "date/time modified" and
"size". If that does not work out for you, specify `-c` or
`--compare-checksums` and paperless will attempt to compare file
checksums instead. This is slower.
checksums instead. This is slower. The manifest and metadata json files
are always updated, unless `cj` or `--compare-json` is specified.
Paperless will not remove any existing files in the export directory. If
you want paperless to also remove files that do not belong to the
@@ -622,3 +624,12 @@ document_fuzzy_match [--ratio] [--processes N]
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.
### Prune history (audit log) entries {#prune-history}
If the audit log is enabled Paperless-ngx keeps an audit log of all changes made to documents. Functionality to automatically remove entries for deleted documents was added but
entries created prior to this are not removed. This command allows you to prune the audit log of entries that are no longer needed.
```shell
prune_audit_logs
```

View File

@@ -308,7 +308,7 @@ Paperless provides the following variables for use within filenames:
- `{{ tag_list }}`: A comma separated list of all tags assigned to the
document.
- `{{ title }}`: The title of the document.
- `{{ created }}`: The full date (ISO format) the document was created.
- `{{ created }}`: The full date (ISO 8601 format, e.g. `2024-03-14`) the document was created.
- `{{ created_year }}`: Year created only, formatted as the year with
century.
- `{{ created_year_short }}`: Year created only, formatted as the year
@@ -476,7 +476,7 @@ a document with an ASN of 355 would be placed in `somepath/asn-201-400/asn-3xx/T
/{{ title }}
```
For a PDF document, it would result in `pdfs/Title.pdf`, but for a PNG document, the path would be `pngs/Title.pdf`.
For a PDF document, it would result in `pdfs/Title.pdf`, but for a PNG document, the path would be `pngs/Title.png`.
To use custom fields:
@@ -585,10 +585,13 @@ services:
### Case Sensitivity
The database interface does not provide a method to configure a MySQL
database to be case sensitive. This would prevent a user from creating a
database to be case-sensitive. A case-**in**sensitive database prevents a user from creating a
tag `Name` and `NAME` as they are considered the same.
Per Django documentation, to enable this requires manual intervention.
However, there is a downside to turning on case sensitivity, as it also makes searches case-sensitive,
so for example a document with the title `Invoice` won't be found when searching for `invoice`.
Per Django documentation, making a database case-sensitive requires manual intervention.
To enable case sensitive tables, you can execute the following command
against each table:
@@ -605,6 +608,8 @@ 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).
For more information on this topic, you can refer to [this](https://code.djangoproject.com/ticket/9682) Django issue.
### Missing timezones
MySQL as well as MariaDB do not have any timezone information by default (though some
@@ -806,13 +811,13 @@ gpg --decrypt name_of_file.asc
First, enable the [PAPERLESS_ENABLE_GPG_DECRYPTOR environment variable](configuration.md#PAPERLESS_ENABLE_GPG_DECRYPTOR).
Then determine your local `gpg-agent.extra` socket by invoking
Then determine your local `gpg-agent` socket by invoking
```
gpgconf --list-dir agent-extra-socket
gpgconf --list-dir agent-socket
```
on your host. A possible output is `~/.gnupg/S.gpg-agent.extra`.
on your host. A possible output is `~/.gnupg/S.gpg-agent`.
Also find the location of your public keyring.
If using docker, you'll need to add the following volume mounts to your `docker-compose.yml` file:
@@ -821,7 +826,7 @@ If using docker, you'll need to add the following volume mounts to your `docker-
webserver:
volumes:
- /home/user/.gnupg/pubring.gpg:/usr/src/paperless/.gnupg/pubring.gpg
- <path to gpg-agent.extra socket>:/usr/src/paperless/.gnupg/S.gpg-agent
- <path to gpg-agent socket>:/usr/src/paperless/.gnupg/S.gpg-agent
```
For a 'bare-metal' installation no further configuration is necessary. If you

View File

@@ -365,6 +365,10 @@ The endpoint supports the following optional form fields:
- `custom_fields`: An array of custom field ids to assign (with an empty
value) to the document.
!!! note
Sending a `Content-Length` header with correct size is mandatory.
The endpoint will immediately return HTTP 200 if the document consumption
process was started successfully, with the UUID of the consumption task
as the data. No additional status information about the consumption process
@@ -473,6 +477,11 @@ The following methods are supported:
- Requires `parameters`:
- `"pages": [..]` The list should be a list of integers e.g. `"[2,3,4]"`
- The delete_pages operation only accepts a single document.
- `modify_custom_fields`
- Requires `parameters`:
- `"add_custom_fields": { CUSTOM_FIELD_ID: VALUE }`: JSON object consisting of custom field id:value pairs to add to the document, can also be a list of custom field IDs
to add with empty values.
- `"remove_custom_fields": [CUSTOM_FIELD_ID]`: custom field ids to remove from the document.
### Objects
@@ -556,3 +565,11 @@ Initial API version.
- Consumption templates were refactored to workflows and API endpoints
changed as such.
#### Version 5
- Added bulk deletion methods for documents and objects.
#### Version 6
- Moved acknowledge tasks endpoint to be under `/api/tasks/acknowledge/`.

View File

@@ -1,5 +1,390 @@
# Changelog
## paperless-ngx 2.14.5
### Features
- Change: restrict altering and creation of superusers to superusers only [@shamoon](https://github.com/shamoon) ([#8837](https://github.com/paperless-ngx/paperless-ngx/pull/8837))
### Bug Fixes
- Fix: fix long tag visual wrapping [@shamoon](https://github.com/shamoon) ([#8833](https://github.com/paperless-ngx/paperless-ngx/pull/8833))
- Fix: Enforce classifier training ordering to prevent extra training [@stumpylog](https://github.com/stumpylog) ([#8822](https://github.com/paperless-ngx/paperless-ngx/pull/8822))
- Fix: import router module to not found component [@shamoon](https://github.com/shamoon) ([#8821](https://github.com/paperless-ngx/paperless-ngx/pull/8821))
- Fix: better reflect some mail account / rule permissions in UI [@shamoon](https://github.com/shamoon) ([#8812](https://github.com/paperless-ngx/paperless-ngx/pull/8812))
### Dependencies
- Chore(deps-dev): Bump undici from 5.28.4 to 5.28.5 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8851](https://github.com/paperless-ngx/paperless-ngx/pull/8851))
- Chore(deps-dev): Bump the development group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#8841](https://github.com/paperless-ngx/paperless-ngx/pull/8841))
### All App Changes
<details>
<summary>9 changes</summary>
- Chore(deps-dev): Bump undici from 5.28.4 to 5.28.5 in /src-ui @[dependabot[bot]](https://github.com/apps/dependabot) ([#8851](https://github.com/paperless-ngx/paperless-ngx/pull/8851))
- Chore(deps-dev): Bump the development group with 2 updates @[dependabot[bot]](https://github.com/apps/dependabot) ([#8841](https://github.com/paperless-ngx/paperless-ngx/pull/8841))
- Chore: use simpler method for attaching files to emails [@shamoon](https://github.com/shamoon) ([#8845](https://github.com/paperless-ngx/paperless-ngx/pull/8845))
- Change: restrict altering and creation of superusers to superusers only [@shamoon](https://github.com/shamoon) ([#8837](https://github.com/paperless-ngx/paperless-ngx/pull/8837))
- Fix: fix long tag visual wrapping [@shamoon](https://github.com/shamoon) ([#8833](https://github.com/paperless-ngx/paperless-ngx/pull/8833))
- Change: allow generate auth token without a usable password [@shamoon](https://github.com/shamoon) ([#8824](https://github.com/paperless-ngx/paperless-ngx/pull/8824))
- Fix: Enforce classifier training ordering to prevent extra training [@stumpylog](https://github.com/stumpylog) ([#8822](https://github.com/paperless-ngx/paperless-ngx/pull/8822))
- Fix: import router module to not found component [@shamoon](https://github.com/shamoon) ([#8821](https://github.com/paperless-ngx/paperless-ngx/pull/8821))
- Fix: better reflect some mail account / rule permissions in UI [@shamoon](https://github.com/shamoon) ([#8812](https://github.com/paperless-ngx/paperless-ngx/pull/8812))
</details>
## paperless-ngx 2.14.4
### Features
- Enhancement: allow specifying JSON encoding for webhooks [@shamoon](https://github.com/shamoon) ([#8799](https://github.com/paperless-ngx/paperless-ngx/pull/8799))
- Change: disable API basic auth if MFA enabled [@shamoon](https://github.com/shamoon) ([#8792](https://github.com/paperless-ngx/paperless-ngx/pull/8792))
### Bug Fixes
- Fix: Include email and webhook objects in the export [@stumpylog](https://github.com/stumpylog) ([#8790](https://github.com/paperless-ngx/paperless-ngx/pull/8790))
- Fix: use MIMEBase for email attachments [@shamoon](https://github.com/shamoon) ([#8762](https://github.com/paperless-ngx/paperless-ngx/pull/8762))
- Fix: handle page out of range in mgmt lists after delete [@shamoon](https://github.com/shamoon) ([#8771](https://github.com/paperless-ngx/paperless-ngx/pull/8771))
### All App Changes
<details>
<summary>5 changes</summary>
- Enhancement: allow specifying JSON encoding for webhooks [@shamoon](https://github.com/shamoon) ([#8799](https://github.com/paperless-ngx/paperless-ngx/pull/8799))
- Change: disable API basic auth if MFA enabled [@shamoon](https://github.com/shamoon) ([#8792](https://github.com/paperless-ngx/paperless-ngx/pull/8792))
- Fix: Include email and webhook objects in the export [@stumpylog](https://github.com/stumpylog) ([#8790](https://github.com/paperless-ngx/paperless-ngx/pull/8790))
- Fix: use MIMEBase for email attachments [@shamoon](https://github.com/shamoon) ([#8762](https://github.com/paperless-ngx/paperless-ngx/pull/8762))
- Fix: handle page out of range in mgmt lists after delete [@shamoon](https://github.com/shamoon) ([#8771](https://github.com/paperless-ngx/paperless-ngx/pull/8771))
</details>
## paperless-ngx 2.14.3
### Bug Fixes
- Fix: Adds a default 30s timeout for emails, instead of no timeout [@stumpylog](https://github.com/stumpylog) ([#8757](https://github.com/paperless-ngx/paperless-ngx/pull/8757))
- Fix: import forms modules for entries component [@shamoon](https://github.com/shamoon) ([#8752](https://github.com/paperless-ngx/paperless-ngx/pull/8752))
- Fix: fix email/wh actions on consume started [@shamoon](https://github.com/shamoon) ([#8750](https://github.com/paperless-ngx/paperless-ngx/pull/8750))
- Fix: import date picker module in cf query dropdown [@shamoon](https://github.com/shamoon) ([#8749](https://github.com/paperless-ngx/paperless-ngx/pull/8749))
### All App Changes
<details>
<summary>5 changes</summary>
- Fix: Adds a default 30s timeout for emails, instead of no timeout [@stumpylog](https://github.com/stumpylog) ([#8757](https://github.com/paperless-ngx/paperless-ngx/pull/8757))
- Enhancement: set autofocus on MFA code field [@mxmehl](https://github.com/mxmehl) ([#8756](https://github.com/paperless-ngx/paperless-ngx/pull/8756))
- Fix: import forms modules for entries component [@shamoon](https://github.com/shamoon) ([#8752](https://github.com/paperless-ngx/paperless-ngx/pull/8752))
- Fix: fix email/wh actions on consume started [@shamoon](https://github.com/shamoon) ([#8750](https://github.com/paperless-ngx/paperless-ngx/pull/8750))
- Fix: import date picker module in cf query dropdown [@shamoon](https://github.com/shamoon) ([#8749](https://github.com/paperless-ngx/paperless-ngx/pull/8749))
</details>
## paperless-ngx 2.14.2
### Bug Fixes
- Fix: dont try to parse empty webhook params [@shamoon](https://github.com/shamoon) ([#8742](https://github.com/paperless-ngx/paperless-ngx/pull/8742))
- Fix: pass working file to workflows, pickle file bytes [@shamoon](https://github.com/shamoon) ([#8741](https://github.com/paperless-ngx/paperless-ngx/pull/8741))
- Fix: use hard delete when bulk editing custom fields [@shamoon](https://github.com/shamoon) ([#8740](https://github.com/paperless-ngx/paperless-ngx/pull/8740))
- Fix: Ensure email attachments use the latest document path for attachments [@stumpylog](https://github.com/stumpylog) ([#8737](https://github.com/paperless-ngx/paperless-ngx/pull/8737))
- Fix: include tooltip module for custom fields display [@shamoon](https://github.com/shamoon) ([#8739](https://github.com/paperless-ngx/paperless-ngx/pull/8739))
- Fix: remove id of webhook/email actions on copy [@shamoon](https://github.com/shamoon) ([#8729](https://github.com/paperless-ngx/paperless-ngx/pull/8729))
- Fix: import dnd module for merge confirm dialog [@shamoon](https://github.com/shamoon) ([#8727](https://github.com/paperless-ngx/paperless-ngx/pull/8727))
### Dependencies
- Chore(deps): Bump django from 5.1.4 to 5.1.5 [@dependabot](https://github.com/dependabot) ([#8738](https://github.com/paperless-ngx/paperless-ngx/pull/8738))
### All App Changes
<details>
<summary>7 changes</summary>
- Fix: dont try to parse empty webhook params [@shamoon](https://github.com/shamoon) ([#8742](https://github.com/paperless-ngx/paperless-ngx/pull/8742))
- Fix: pass working file to workflows, pickle file bytes [@shamoon](https://github.com/shamoon) ([#8741](https://github.com/paperless-ngx/paperless-ngx/pull/8741))
- Fix: use hard delete when bulk editing custom fields [@shamoon](https://github.com/shamoon) ([#8740](https://github.com/paperless-ngx/paperless-ngx/pull/8740))
- Fix: Ensure email attachments use the latest document path for attachments [@stumpylog](https://github.com/stumpylog) ([#8737](https://github.com/paperless-ngx/paperless-ngx/pull/8737))
- Fix: include tooltip module for custom fields display [@shamoon](https://github.com/shamoon) ([#8739](https://github.com/paperless-ngx/paperless-ngx/pull/8739))
- Fix: remove id of webhook/email actions on copy [@shamoon](https://github.com/shamoon) ([#8729](https://github.com/paperless-ngx/paperless-ngx/pull/8729))
- Fix: import dnd module for merge confirm dialog [@shamoon](https://github.com/shamoon) ([#8727](https://github.com/paperless-ngx/paperless-ngx/pull/8727))
</details>
## paperless-ngx 2.14.1
### Bug Fixes
- Fix: prevent error if bulk edit method not in MODIFIED_FIELD_BY_METHOD [@shamoon](https://github.com/shamoon) ([#8710](https://github.com/paperless-ngx/paperless-ngx/pull/8710))
- Fix: include tag component in list view [@shamoon](https://github.com/shamoon) ([#8706](https://github.com/paperless-ngx/paperless-ngx/pull/8706))
- Fix: use unmodified original for checksum if exists [@shamoon](https://github.com/shamoon) ([#8693](https://github.com/paperless-ngx/paperless-ngx/pull/8693))
- Fix: complete load with native PDF viewer [@shamoon](https://github.com/shamoon) ([#8699](https://github.com/paperless-ngx/paperless-ngx/pull/8699))
### All App Changes
<details>
<summary>4 changes</summary>
- Fix: prevent error if bulk edit method not in MODIFIED_FIELD_BY_METHOD [@shamoon](https://github.com/shamoon) ([#8710](https://github.com/paperless-ngx/paperless-ngx/pull/8710))
- Fix: include tag component in list view [@shamoon](https://github.com/shamoon) ([#8706](https://github.com/paperless-ngx/paperless-ngx/pull/8706))
- Fix: use unmodified original for checksum if exists [@shamoon](https://github.com/shamoon) ([#8693](https://github.com/paperless-ngx/paperless-ngx/pull/8693))
- Fix: complete load with native PDF viewer [@shamoon](https://github.com/shamoon) ([#8699](https://github.com/paperless-ngx/paperless-ngx/pull/8699))
</details>
## paperless-ngx 2.14.0
### Features
- Enhancement: custom field sorting [@shamoon](https://github.com/shamoon) ([#8494](https://github.com/paperless-ngx/paperless-ngx/pull/8494))
- Enhancement: process mail button [@shamoon](https://github.com/shamoon) ([#8466](https://github.com/paperless-ngx/paperless-ngx/pull/8466))
- Feature: bulk edit custom field values [@shamoon](https://github.com/shamoon) ([#8428](https://github.com/paperless-ngx/paperless-ngx/pull/8428))
- Enhancement: improved loading visuals [@shamoon](https://github.com/shamoon) ([#8435](https://github.com/paperless-ngx/paperless-ngx/pull/8435))
- Enhancement: prune audit logs and management command [@shamoon](https://github.com/shamoon) ([#8416](https://github.com/paperless-ngx/paperless-ngx/pull/8416))
- Change: make saved views manage its own component [@shamoon](https://github.com/shamoon) ([#8423](https://github.com/paperless-ngx/paperless-ngx/pull/8423))
- Enhancement: file task filtering [@shamoon](https://github.com/shamoon) ([#8421](https://github.com/paperless-ngx/paperless-ngx/pull/8421))
- Enhancement: auto-link duplicate document [@shamoon](https://github.com/shamoon) ([#8415](https://github.com/paperless-ngx/paperless-ngx/pull/8415))
- Feature: email, webhook workflow actions [@shamoon](https://github.com/shamoon) ([#8108](https://github.com/paperless-ngx/paperless-ngx/pull/8108))
- Enhancement: use stable unique IDs for custom field select options [@shamoon](https://github.com/shamoon) ([#8299](https://github.com/paperless-ngx/paperless-ngx/pull/8299))
- Enhancement: better TIFF display browser support [@shamoon](https://github.com/shamoon) ([#8087](https://github.com/paperless-ngx/paperless-ngx/pull/8087))
- Enhancement: filterable list count sorting and opacification [@shamoon](https://github.com/shamoon) ([#8386](https://github.com/paperless-ngx/paperless-ngx/pull/8386))
- Enhancement: preview button for document list and trash, refactor [@shamoon](https://github.com/shamoon) ([#8384](https://github.com/paperless-ngx/paperless-ngx/pull/8384))
- Enhancement: use theme-color meta tag [@shamoon](https://github.com/shamoon) ([#8359](https://github.com/paperless-ngx/paperless-ngx/pull/8359))
- Feature: scheduled workflow trigger [@shamoon](https://github.com/shamoon) ([#8036](https://github.com/paperless-ngx/paperless-ngx/pull/8036))
- Enhancement: support owner permissions for file tasks [@shamoon](https://github.com/shamoon) ([#8195](https://github.com/paperless-ngx/paperless-ngx/pull/8195))
- Fixhancement: change update content to handle archive disabled [@shamoon](https://github.com/shamoon) ([#8315](https://github.com/paperless-ngx/paperless-ngx/pull/8315))
- Enhancement: next / previous shortcuts for document list [@shamoon](https://github.com/shamoon) ([#8309](https://github.com/paperless-ngx/paperless-ngx/pull/8309))
- Feature: two-factor authentication [@shamoon](https://github.com/shamoon) ([#8012](https://github.com/paperless-ngx/paperless-ngx/pull/8012))
- Enhancement: save \& next / close shortcut key [@shamoon](https://github.com/shamoon) ([#8243](https://github.com/paperless-ngx/paperless-ngx/pull/8243))
- Feature: loading preview, better text popup preview [@shamoon](https://github.com/shamoon) ([#8011](https://github.com/paperless-ngx/paperless-ngx/pull/8011))
### Bug Fixes
- Fix: add some minor frontend permissions checks [@shamoon](https://github.com/shamoon) ([#8524](https://github.com/paperless-ngx/paperless-ngx/pull/8524))
- FIx: obliquely trim spaces from global search [@shamoon](https://github.com/shamoon) ([#8484](https://github.com/paperless-ngx/paperless-ngx/pull/8484))
- Fix: include global perms for bulk edit endpoint [@shamoon](https://github.com/shamoon) ([#8468](https://github.com/paperless-ngx/paperless-ngx/pull/8468))
- Fix: frontend better reflect global perms for bulk edit, disabled form state [@shamoon](https://github.com/shamoon) ([#8469](https://github.com/paperless-ngx/paperless-ngx/pull/8469))
- Fixhancement: dispatch change event from current field prior to save [@shamoon](https://github.com/shamoon) ([#8369](https://github.com/paperless-ngx/paperless-ngx/pull/8369))
- Fix: Fixes install script to handle languages with dashes or underscores [@stumpylog](https://github.com/stumpylog) ([#8341](https://github.com/paperless-ngx/paperless-ngx/pull/8341))
- Fix: handle very old dates with positive offset too [@shamoon](https://github.com/shamoon) ([#8335](https://github.com/paperless-ngx/paperless-ngx/pull/8335))
- Fixhancement: change update content to handle archive disabled [@shamoon](https://github.com/shamoon) ([#8315](https://github.com/paperless-ngx/paperless-ngx/pull/8315))
- Fix: include db_index caveat in squashed migrations [@shamoon](https://github.com/shamoon) ([#8292](https://github.com/paperless-ngx/paperless-ngx/pull/8292))
- Fix: prevent duplicate workflow runs [@shamoon](https://github.com/shamoon) ([#8268](https://github.com/paperless-ngx/paperless-ngx/pull/8268))
- Fix: add note about select options to edit dialog [@shamoon](https://github.com/shamoon) ([#8267](https://github.com/paperless-ngx/paperless-ngx/pull/8267))
### Maintenance
- Chore(deps): Bump codecov/codecov-action from 4 to 5 in the actions group [@dependabot](https://github.com/dependabot) ([#8401](https://github.com/paperless-ngx/paperless-ngx/pull/8401))
### Dependencies
<details>
<summary>16 changes</summary>
- Chore(deps): Bump the small-changes group with 3 updates [@dependabot](https://github.com/dependabot) ([#8627](https://github.com/paperless-ngx/paperless-ngx/pull/8627))
- Chore(deps-dev): Bump ruff from 0.8.4 to 0.8.6 in the development group [@dependabot](https://github.com/dependabot) ([#8626](https://github.com/paperless-ngx/paperless-ngx/pull/8626))
- Chore(deps): Bump django-allauth from 65.3.0 to 65.3.1 in the django group [@dependabot](https://github.com/dependabot) ([#8574](https://github.com/paperless-ngx/paperless-ngx/pull/8574))
- Chore(deps-dev): Bump ruff from 0.8.3 to 0.8.4 in the development group [@dependabot](https://github.com/dependabot) ([#8546](https://github.com/paperless-ngx/paperless-ngx/pull/8546))
- Chore(deps): Bump the small-changes group with 6 updates [@dependabot](https://github.com/dependabot) ([#8547](https://github.com/paperless-ngx/paperless-ngx/pull/8547))
- Chore: update ng2 pdf viewer [@shamoon](https://github.com/shamoon) ([#8462](https://github.com/paperless-ngx/paperless-ngx/pull/8462))
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#8458](https://github.com/paperless-ngx/paperless-ngx/pull/8458))
- Chore(deps): Bump django-soft-delete from 1.0.15 to 1.0.16 in the django group [@dependabot](https://github.com/dependabot) ([#8459](https://github.com/paperless-ngx/paperless-ngx/pull/8459))
- Chore(deps): Bump the small-changes group with 4 updates [@dependabot](https://github.com/dependabot) ([#8460](https://github.com/paperless-ngx/paperless-ngx/pull/8460))
- Chore(deps): Bump django from 5.1.3 to 5.1.4 [@dependabot](https://github.com/dependabot) ([#8445](https://github.com/paperless-ngx/paperless-ngx/pull/8445))
- Chore(deps-dev): Bump the development group with 4 updates [@dependabot](https://github.com/dependabot) ([#8414](https://github.com/paperless-ngx/paperless-ngx/pull/8414))
- Chore(deps): Bump codecov/codecov-action from 4 to 5 in the actions group [@dependabot](https://github.com/dependabot) ([#8401](https://github.com/paperless-ngx/paperless-ngx/pull/8401))
- Chore(deps-dev): Bump the development group with 4 updates [@dependabot](https://github.com/dependabot) ([#8352](https://github.com/paperless-ngx/paperless-ngx/pull/8352))
- Chore(deps): Bump the small-changes group across 1 directory with 7 updates [@dependabot](https://github.com/dependabot) ([#8399](https://github.com/paperless-ngx/paperless-ngx/pull/8399))
- Chore(deps): Bump tornado from 6.4.1 to 6.4.2 [@dependabot](https://github.com/dependabot) ([#8336](https://github.com/paperless-ngx/paperless-ngx/pull/8336))
- Chore(deps): Bump watchdog from 5.0.3 to 6.0.0 in the major-versions group [@dependabot](https://github.com/dependabot) ([#8257](https://github.com/paperless-ngx/paperless-ngx/pull/8257))
</details>
### All App Changes
<details>
<summary>65 changes</summary>
- Fix: use state param with oauth [@shamoon](https://github.com/shamoon) ([#8636](https://github.com/paperless-ngx/paperless-ngx/pull/8636))
- Fix: check permissions for all documents via bulk download [@shamoon](https://github.com/shamoon) ([#8631](https://github.com/paperless-ngx/paperless-ngx/pull/8631))
- Chore(deps): Bump the small-changes group with 3 updates [@dependabot](https://github.com/dependabot) ([#8627](https://github.com/paperless-ngx/paperless-ngx/pull/8627))
- Chore(deps-dev): Bump ruff from 0.8.4 to 0.8.6 in the development group [@dependabot](https://github.com/dependabot) ([#8626](https://github.com/paperless-ngx/paperless-ngx/pull/8626))
- Chore: Switch from os.path to pathlib.Path [@gothicVI](https://github.com/gothicVI) ([#8325](https://github.com/paperless-ngx/paperless-ngx/pull/8325))
- Chore: disable max-age for some document endpoints [@tsia](https://github.com/tsia) ([#8611](https://github.com/paperless-ngx/paperless-ngx/pull/8611))
- Fix: do not accept empty string for doc link value via API [@shamoon](https://github.com/shamoon) ([#8596](https://github.com/paperless-ngx/paperless-ngx/pull/8596))
- Enhancement: angular 19 [@shamoon](https://github.com/shamoon) ([#8584](https://github.com/paperless-ngx/paperless-ngx/pull/8584))
- Fix: fix hotkey arrows [@shamoon](https://github.com/shamoon) ([#8583](https://github.com/paperless-ngx/paperless-ngx/pull/8583))
- Chore: remove outdated admin logentry handler [@shamoon](https://github.com/shamoon) ([#8580](https://github.com/paperless-ngx/paperless-ngx/pull/8580))
- Chore(deps): Bump django-allauth from 65.3.0 to 65.3.1 in the django group [@dependabot](https://github.com/dependabot) ([#8574](https://github.com/paperless-ngx/paperless-ngx/pull/8574))
- Enhancement: custom field sorting [@shamoon](https://github.com/shamoon) ([#8494](https://github.com/paperless-ngx/paperless-ngx/pull/8494))
- Fix: fix occasional error toast overflow [@shamoon](https://github.com/shamoon) ([#8552](https://github.com/paperless-ngx/paperless-ngx/pull/8552))
- Fix: fix share link archive version detection [@shamoon](https://github.com/shamoon) ([#8551](https://github.com/paperless-ngx/paperless-ngx/pull/8551))
- Chore(deps-dev): Bump ruff from 0.8.3 to 0.8.4 in the development group [@dependabot](https://github.com/dependabot) ([#8546](https://github.com/paperless-ngx/paperless-ngx/pull/8546))
- Chore(deps): Bump the small-changes group with 6 updates [@dependabot](https://github.com/dependabot) ([#8547](https://github.com/paperless-ngx/paperless-ngx/pull/8547))
- Enhancement: add timeout for Tika client [@HiranChaudhuri](https://github.com/HiranChaudhuri) ([#8520](https://github.com/paperless-ngx/paperless-ngx/pull/8520))
- Fix: add some minor frontend permissions checks [@shamoon](https://github.com/shamoon) ([#8524](https://github.com/paperless-ngx/paperless-ngx/pull/8524))
- FIx: obliquely trim spaces from global search [@shamoon](https://github.com/shamoon) ([#8484](https://github.com/paperless-ngx/paperless-ngx/pull/8484))
- Fix: include global perms for bulk edit endpoint [@shamoon](https://github.com/shamoon) ([#8468](https://github.com/paperless-ngx/paperless-ngx/pull/8468))
- Enhancement: process mail button [@shamoon](https://github.com/shamoon) ([#8466](https://github.com/paperless-ngx/paperless-ngx/pull/8466))
- Fix: frontend better reflect global perms for bulk edit, disabled form state [@shamoon](https://github.com/shamoon) ([#8469](https://github.com/paperless-ngx/paperless-ngx/pull/8469))
- Chore: update ng2 pdf viewer [@shamoon](https://github.com/shamoon) ([#8462](https://github.com/paperless-ngx/paperless-ngx/pull/8462))
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#8458](https://github.com/paperless-ngx/paperless-ngx/pull/8458))
- Chore(deps): Bump django-soft-delete from 1.0.15 to 1.0.16 in the django group [@dependabot](https://github.com/dependabot) ([#8459](https://github.com/paperless-ngx/paperless-ngx/pull/8459))
- Chore(deps): Bump the small-changes group with 4 updates [@dependabot](https://github.com/dependabot) ([#8460](https://github.com/paperless-ngx/paperless-ngx/pull/8460))
- Chore: use rxjs instead of JS setInterval for timers [@shamoon](https://github.com/shamoon) ([#8461](https://github.com/paperless-ngx/paperless-ngx/pull/8461))
- Feature: bulk edit custom field values [@shamoon](https://github.com/shamoon) ([#8428](https://github.com/paperless-ngx/paperless-ngx/pull/8428))
- Enhancement: improved loading visuals [@shamoon](https://github.com/shamoon) ([#8435](https://github.com/paperless-ngx/paperless-ngx/pull/8435))
- Enhancement: prune audit logs and management command [@shamoon](https://github.com/shamoon) ([#8416](https://github.com/paperless-ngx/paperless-ngx/pull/8416))
- Change: make saved views manage its own component [@shamoon](https://github.com/shamoon) ([#8423](https://github.com/paperless-ngx/paperless-ngx/pull/8423))
- Enhancement: file task filtering [@shamoon](https://github.com/shamoon) ([#8421](https://github.com/paperless-ngx/paperless-ngx/pull/8421))
- Enhancement: auto-link duplicate document [@shamoon](https://github.com/shamoon) ([#8415](https://github.com/paperless-ngx/paperless-ngx/pull/8415))
- Enhancement: include current filename placeholder in workflows @Lu-Fi ([#8319](https://github.com/paperless-ngx/paperless-ngx/pull/8319))
- Feature: email, webhook workflow actions [@shamoon](https://github.com/shamoon) ([#8108](https://github.com/paperless-ngx/paperless-ngx/pull/8108))
- Chore(deps-dev): Bump the development group with 4 updates [@dependabot](https://github.com/dependabot) ([#8414](https://github.com/paperless-ngx/paperless-ngx/pull/8414))
- Enhancement: use stable unique IDs for custom field select options [@shamoon](https://github.com/shamoon) ([#8299](https://github.com/paperless-ngx/paperless-ngx/pull/8299))
- Chore(deps-dev): Bump the development group with 4 updates [@dependabot](https://github.com/dependabot) ([#8352](https://github.com/paperless-ngx/paperless-ngx/pull/8352))
- Enhancement: better TIFF display browser support [@shamoon](https://github.com/shamoon) ([#8087](https://github.com/paperless-ngx/paperless-ngx/pull/8087))
- Chore(deps): Bump the small-changes group across 1 directory with 7 updates [@dependabot](https://github.com/dependabot) ([#8399](https://github.com/paperless-ngx/paperless-ngx/pull/8399))
- Enhancement: History (audit log) for bulk edit operations [@shamoon](https://github.com/shamoon) ([#8196](https://github.com/paperless-ngx/paperless-ngx/pull/8196))
- Enhancement: larger previews in action dialogs [@shamoon](https://github.com/shamoon) ([#8387](https://github.com/paperless-ngx/paperless-ngx/pull/8387))
- Enhancement: filterable list count sorting and opacification [@shamoon](https://github.com/shamoon) ([#8386](https://github.com/paperless-ngx/paperless-ngx/pull/8386))
- Enhancement: preview button for document list and trash, refactor [@shamoon](https://github.com/shamoon) ([#8384](https://github.com/paperless-ngx/paperless-ngx/pull/8384))
- Fixhancement: dispatch change event from current field prior to save [@shamoon](https://github.com/shamoon) ([#8369](https://github.com/paperless-ngx/paperless-ngx/pull/8369))
- Enhancement: use theme-color meta tag [@shamoon](https://github.com/shamoon) ([#8359](https://github.com/paperless-ngx/paperless-ngx/pull/8359))
- Chore: cleanup urls, use actions for some views [@shamoon](https://github.com/shamoon) ([#8346](https://github.com/paperless-ngx/paperless-ngx/pull/8346))
- Feature: scheduled workflow trigger [@shamoon](https://github.com/shamoon) ([#8036](https://github.com/paperless-ngx/paperless-ngx/pull/8036))
- Fix: handle very old dates with positive offset too [@shamoon](https://github.com/shamoon) ([#8335](https://github.com/paperless-ngx/paperless-ngx/pull/8335))
- Refactor: fix unnecessary use of filterable dropdown sorting [@shamoon](https://github.com/shamoon) ([#8328](https://github.com/paperless-ngx/paperless-ngx/pull/8328))
- Enhancement: offer link to restored document [@shamoon](https://github.com/shamoon) ([#8321](https://github.com/paperless-ngx/paperless-ngx/pull/8321))
- Enhancement: support owner permissions for file tasks [@shamoon](https://github.com/shamoon) ([#8195](https://github.com/paperless-ngx/paperless-ngx/pull/8195))
- Fixhancement: change update content to handle archive disabled [@shamoon](https://github.com/shamoon) ([#8315](https://github.com/paperless-ngx/paperless-ngx/pull/8315))
- Chore(deps): Bump watchdog from 5.0.3 to 6.0.0 in the major-versions group [@dependabot](https://github.com/dependabot) ([#8257](https://github.com/paperless-ngx/paperless-ngx/pull/8257))
- Enhancement: Add --compare-json option to document_exporter to write json files only if changed [@kdoren](https://github.com/kdoren) ([#8261](https://github.com/paperless-ngx/paperless-ngx/pull/8261))
- Enhancement: next / previous shortcuts for document list [@shamoon](https://github.com/shamoon) ([#8309](https://github.com/paperless-ngx/paperless-ngx/pull/8309))
- Feature: two-factor authentication [@shamoon](https://github.com/shamoon) ([#8012](https://github.com/paperless-ngx/paperless-ngx/pull/8012))
- Fix: include db_index caveat in squashed migrations [@shamoon](https://github.com/shamoon) ([#8292](https://github.com/paperless-ngx/paperless-ngx/pull/8292))
- Tweak: use fixed position for navbar [@shamoon](https://github.com/shamoon) ([#8279](https://github.com/paperless-ngx/paperless-ngx/pull/8279))
- Fix: prevent duplicate workflow runs [@shamoon](https://github.com/shamoon) ([#8268](https://github.com/paperless-ngx/paperless-ngx/pull/8268))
- Fix: add note about select options to edit dialog [@shamoon](https://github.com/shamoon) ([#8267](https://github.com/paperless-ngx/paperless-ngx/pull/8267))
- Enhancement: save \& next / close shortcut key [@shamoon](https://github.com/shamoon) ([#8243](https://github.com/paperless-ngx/paperless-ngx/pull/8243))
- Feature: loading preview, better text popup preview [@shamoon](https://github.com/shamoon) ([#8011](https://github.com/paperless-ngx/paperless-ngx/pull/8011))
- Chore: switch src/documents/bulk\*.py from os.path to pathlib.Path [@gothicVI](https://github.com/gothicVI) ([#7862](https://github.com/paperless-ngx/paperless-ngx/pull/7862))
- Chore: Bulk backend dependency updates [@stumpylog](https://github.com/stumpylog) ([#8212](https://github.com/paperless-ngx/paperless-ngx/pull/8212))
</details>
## paperless-ngx 2.13.5
### Bug Fixes
- Fix: handle page count exception for pw-protected files [@shamoon](https://github.com/shamoon) ([#8240](https://github.com/paperless-ngx/paperless-ngx/pull/8240))
- Fix: correctly track task id in list for change detection [@shamoon](https://github.com/shamoon) ([#8230](https://github.com/paperless-ngx/paperless-ngx/pull/8230))
- Fix: Admin pages should show trashed documents [@stumpylog](https://github.com/stumpylog) ([#8068](https://github.com/paperless-ngx/paperless-ngx/pull/8068))
- Fix: tag colors shouldn't change when selected in list [@shamoon](https://github.com/shamoon) ([#8225](https://github.com/paperless-ngx/paperless-ngx/pull/8225))
- Fix: fix re-activation of save button when changing array items [@shamoon](https://github.com/shamoon) ([#8208](https://github.com/paperless-ngx/paperless-ngx/pull/8208))
- Fix: fix thumbnail clipping, select inverted color in safari dark mode not system [@shamoon](https://github.com/shamoon) ([#8193](https://github.com/paperless-ngx/paperless-ngx/pull/8193))
- Fix: select checkbox should remain visible [@shamoon](https://github.com/shamoon) ([#8185](https://github.com/paperless-ngx/paperless-ngx/pull/8185))
- Fix: warn with proper error on ASN exists in trash [@shamoon](https://github.com/shamoon) ([#8176](https://github.com/paperless-ngx/paperless-ngx/pull/8176))
### Maintenance
- Chore: Updates all runner images to use Ubuntu Noble [@stumpylog](https://github.com/stumpylog) ([#8213](https://github.com/paperless-ngx/paperless-ngx/pull/8213))
- Chore(deps): Bump stumpylog/image-cleaner-action from 0.8.0 to 0.9.0 in the actions group [@dependabot](https://github.com/dependabot) ([#8142](https://github.com/paperless-ngx/paperless-ngx/pull/8142))
### Dependencies
- Chore(deps): Bump stumpylog/image-cleaner-action from 0.8.0 to 0.9.0 in the actions group [@dependabot](https://github.com/dependabot) ([#8142](https://github.com/paperless-ngx/paperless-ngx/pull/8142))
### All App Changes
<details>
<summary>7 changes</summary>
- Fix: handle page count exception for pw-protected files [@shamoon](https://github.com/shamoon) ([#8240](https://github.com/paperless-ngx/paperless-ngx/pull/8240))
- Fix: correctly track task id in list for change detection [@shamoon](https://github.com/shamoon) ([#8230](https://github.com/paperless-ngx/paperless-ngx/pull/8230))
- Fix: Admin pages should show trashed documents [@stumpylog](https://github.com/stumpylog) ([#8068](https://github.com/paperless-ngx/paperless-ngx/pull/8068))
- Fix: tag colors shouldn't change when selected in list [@shamoon](https://github.com/shamoon) ([#8225](https://github.com/paperless-ngx/paperless-ngx/pull/8225))
- Fix: fix re-activation of save button when changing array items [@shamoon](https://github.com/shamoon) ([#8208](https://github.com/paperless-ngx/paperless-ngx/pull/8208))
- Fix: fix thumbnail clipping, select inverted color in safari dark mode not system [@shamoon](https://github.com/shamoon) ([#8193](https://github.com/paperless-ngx/paperless-ngx/pull/8193))
- Fix: select checkbox should remain visible [@shamoon](https://github.com/shamoon) ([#8185](https://github.com/paperless-ngx/paperless-ngx/pull/8185))
- Fix: warn with proper error on ASN exists in trash [@shamoon](https://github.com/shamoon) ([#8176](https://github.com/paperless-ngx/paperless-ngx/pull/8176))
</details>
## paperless-ngx 2.13.4
### Bug Fixes
- Fix: fix dark mode icon blend mode in 2.13.3 [@shamoon](https://github.com/shamoon) ([#8166](https://github.com/paperless-ngx/paperless-ngx/pull/8166))
- Fix: fix clipped popup preview in 2.13.3 [@shamoon](https://github.com/shamoon) ([#8165](https://github.com/paperless-ngx/paperless-ngx/pull/8165))
### All App Changes
<details>
<summary>2 changes</summary>
- Fix: fix dark mode icon blend mode in 2.13.3 [@shamoon](https://github.com/shamoon) ([#8166](https://github.com/paperless-ngx/paperless-ngx/pull/8166))
- Fix: fix clipped popup preview in 2.13.3 [@shamoon](https://github.com/shamoon) ([#8165](https://github.com/paperless-ngx/paperless-ngx/pull/8165))
</details>
## paperless-ngx 2.13.3
### Bug Fixes
- Fix: fix auto-clean PDFs, create parent dir for storing unmodified original [@shamoon](https://github.com/shamoon) ([#8157](https://github.com/paperless-ngx/paperless-ngx/pull/8157))
- Fix: correctly handle exists, false in custom field query filter @yichi-yang ([#8158](https://github.com/paperless-ngx/paperless-ngx/pull/8158))
- Fix: dont use filters for inverted thumbnails in Safari [@shamoon](https://github.com/shamoon) ([#8121](https://github.com/paperless-ngx/paperless-ngx/pull/8121))
- Fix: use static object for activedisplayfields to prevent changes [@shamoon](https://github.com/shamoon) ([#8120](https://github.com/paperless-ngx/paperless-ngx/pull/8120))
- Fix: dont invert pdf colors in FF [@shamoon](https://github.com/shamoon) ([#8110](https://github.com/paperless-ngx/paperless-ngx/pull/8110))
- Fix: make mail account password and refresh token text fields [@shamoon](https://github.com/shamoon) ([#8107](https://github.com/paperless-ngx/paperless-ngx/pull/8107))
### Dependencies
<details>
<summary>8 changes</summary>
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates [@dependabot](https://github.com/dependabot) ([#8145](https://github.com/paperless-ngx/paperless-ngx/pull/8145))
- Chore(deps-dev): Bump @types/node from 22.7.4 to 22.8.6 in /src-ui [@dependabot](https://github.com/dependabot) ([#8148](https://github.com/paperless-ngx/paperless-ngx/pull/8148))
- Chore(deps-dev): Bump @playwright/test from 1.47.2 to 1.48.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#8147](https://github.com/paperless-ngx/paperless-ngx/pull/8147))
- Chore(deps): Bump uuid from 10.0.0 to 11.0.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#8146](https://github.com/paperless-ngx/paperless-ngx/pull/8146))
- Chore(deps): Bump tslib from 2.7.0 to 2.8.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#8149](https://github.com/paperless-ngx/paperless-ngx/pull/8149))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.2.0 to 1.2.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#8150](https://github.com/paperless-ngx/paperless-ngx/pull/8150))
- Chore(deps-dev): Bump @types/jest from 29.5.13 to 29.5.14 in /src-ui in the frontend-jest-dependencies group [@dependabot](https://github.com/dependabot) ([#8144](https://github.com/paperless-ngx/paperless-ngx/pull/8144))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 21 updates [@dependabot](https://github.com/dependabot) ([#8143](https://github.com/paperless-ngx/paperless-ngx/pull/8143))
</details>
### All App Changes
<details>
<summary>14 changes</summary>
- Fix: fix auto-clean PDFs, create parent dir for storing unmodified original [@shamoon](https://github.com/shamoon) ([#8157](https://github.com/paperless-ngx/paperless-ngx/pull/8157))
- Fix: correctly handle exists, false in custom field query filter @yichi-yang ([#8158](https://github.com/paperless-ngx/paperless-ngx/pull/8158))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates [@dependabot](https://github.com/dependabot) ([#8145](https://github.com/paperless-ngx/paperless-ngx/pull/8145))
- Chore(deps-dev): Bump @types/node from 22.7.4 to 22.8.6 in /src-ui [@dependabot](https://github.com/dependabot) ([#8148](https://github.com/paperless-ngx/paperless-ngx/pull/8148))
- Chore(deps-dev): Bump @playwright/test from 1.47.2 to 1.48.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#8147](https://github.com/paperless-ngx/paperless-ngx/pull/8147))
- Chore(deps): Bump uuid from 10.0.0 to 11.0.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#8146](https://github.com/paperless-ngx/paperless-ngx/pull/8146))
- Chore(deps): Bump tslib from 2.7.0 to 2.8.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#8149](https://github.com/paperless-ngx/paperless-ngx/pull/8149))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.2.0 to 1.2.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#8150](https://github.com/paperless-ngx/paperless-ngx/pull/8150))
- Chore(deps-dev): Bump @types/jest from 29.5.13 to 29.5.14 in /src-ui in the frontend-jest-dependencies group [@dependabot](https://github.com/dependabot) ([#8144](https://github.com/paperless-ngx/paperless-ngx/pull/8144))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 21 updates [@dependabot](https://github.com/dependabot) ([#8143](https://github.com/paperless-ngx/paperless-ngx/pull/8143))
- Fix: dont use filters for inverted thumbnails in Safari [@shamoon](https://github.com/shamoon) ([#8121](https://github.com/paperless-ngx/paperless-ngx/pull/8121))
- Fix: use static object for activedisplayfields to prevent changes [@shamoon](https://github.com/shamoon) ([#8120](https://github.com/paperless-ngx/paperless-ngx/pull/8120))
- Fix: dont invert pdf colors in FF [@shamoon](https://github.com/shamoon) ([#8110](https://github.com/paperless-ngx/paperless-ngx/pull/8110))
- Fix: make mail account password and refresh token text fields [@shamoon](https://github.com/shamoon) ([#8107](https://github.com/paperless-ngx/paperless-ngx/pull/8107))
</details>
## paperless-ngx 2.13.2
### Bug Fixes
- Fix: remove auth tokens from export [@shamoon](https://github.com/shamoon) ([#8100](https://github.com/paperless-ngx/paperless-ngx/pull/8100))
- Fix: cf query dropdown styling affecting other components [@shamoon](https://github.com/shamoon) ([#8095](https://github.com/paperless-ngx/paperless-ngx/pull/8095))
### All App Changes
<details>
<summary>2 changes</summary>
- Fix: remove auth tokens from export [@shamoon](https://github.com/shamoon) ([#8100](https://github.com/paperless-ngx/paperless-ngx/pull/8100))
- Fix: cf query dropdown styling affecting other components [@shamoon](https://github.com/shamoon) ([#8095](https://github.com/paperless-ngx/paperless-ngx/pull/8095))
</details>
## paperless-ngx 2.13.1
### Bug Fixes
@@ -42,6 +427,120 @@
- Fix: oauth settings without base url [@shamoon](https://github.com/shamoon) ([#8020](https://github.com/paperless-ngx/paperless-ngx/pull/8020))
</details>
## paperless-ngx 2.13.0
### Notable Changes
- Feature: OAuth2 Gmail and Outlook email support [@shamoon](https://github.com/shamoon) ([#7866](https://github.com/paperless-ngx/paperless-ngx/pull/7866))
- Feature: Enhanced templating for filename format [@stumpylog](https://github.com/stumpylog) ([#7836](https://github.com/paperless-ngx/paperless-ngx/pull/7836))
- Feature: custom fields queries [@shamoon](https://github.com/shamoon) ([#7761](https://github.com/paperless-ngx/paperless-ngx/pull/7761))
- Chore: Drop Python 3.9 support [@stumpylog](https://github.com/stumpylog) ([#7774](https://github.com/paperless-ngx/paperless-ngx/pull/7774))
### Features
- Enhancement: QoL, auto-focus default select field in custom field dropdown [@shamoon](https://github.com/shamoon) ([#7961](https://github.com/paperless-ngx/paperless-ngx/pull/7961))
- Change: open not edit [@shamoon](https://github.com/shamoon) ([#7942](https://github.com/paperless-ngx/paperless-ngx/pull/7942))
- Enhancement: support retain barcode split pages [@shamoon](https://github.com/shamoon) ([#7912](https://github.com/paperless-ngx/paperless-ngx/pull/7912))
- Enhancement: don't wait for doc API to load preview [@shamoon](https://github.com/shamoon) ([#7894](https://github.com/paperless-ngx/paperless-ngx/pull/7894))
- Feature: OAuth2 Gmail and Outlook email support [@shamoon](https://github.com/shamoon) ([#7866](https://github.com/paperless-ngx/paperless-ngx/pull/7866))
- Enhancement: live preview of storage path [@shamoon](https://github.com/shamoon) ([#7870](https://github.com/paperless-ngx/paperless-ngx/pull/7870))
- Enhancement: management list button improvements [@shamoon](https://github.com/shamoon) ([#7848](https://github.com/paperless-ngx/paperless-ngx/pull/7848))
- Enhancement: check for mail destination directory, log post-consume errors [@mrichtarsky](https://github.com/mrichtarsky) ([#7808](https://github.com/paperless-ngx/paperless-ngx/pull/7808))
- Enhancement: workflow overview toggle enable button [@shamoon](https://github.com/shamoon) ([#7818](https://github.com/paperless-ngx/paperless-ngx/pull/7818))
- Enhancement: disable-able mail rules, add toggle to overview [@shamoon](https://github.com/shamoon) ([#7810](https://github.com/paperless-ngx/paperless-ngx/pull/7810))
- Feature: auto-clean some invalid pdfs [@shamoon](https://github.com/shamoon) ([#7651](https://github.com/paperless-ngx/paperless-ngx/pull/7651))
- Feature: page count [@s0llvan](https://github.com/s0llvan) ([#7750](https://github.com/paperless-ngx/paperless-ngx/pull/7750))
- Enhancement: use apt only when needed docker-entrypoint.sh [@gawa971](https://github.com/gawa971) ([#7756](https://github.com/paperless-ngx/paperless-ngx/pull/7756))
- Enhancement: set Django SESSION_EXPIRE_AT_BROWSER_CLOSE from PAPERLESS_ACCOUNT_SESSION_REMEMBER [@shamoon](https://github.com/shamoon) ([#7748](https://github.com/paperless-ngx/paperless-ngx/pull/7748))
- Enhancement: allow setting session cookie age [@shamoon](https://github.com/shamoon) ([#7743](https://github.com/paperless-ngx/paperless-ngx/pull/7743))
- Feature: copy workflows and mail rules, improve layout [@shamoon](https://github.com/shamoon) ([#7727](https://github.com/paperless-ngx/paperless-ngx/pull/7727))
### Bug Fixes
- Fix: remove space before my profile button in dropdown [@tooomm](https://github.com/tooomm) ([#7963](https://github.com/paperless-ngx/paperless-ngx/pull/7963))
- Fix: v2.13.0 RC1 - Handling of Nones when using custom fields in filepath templating [@stumpylog](https://github.com/stumpylog) ([#7933](https://github.com/paperless-ngx/paperless-ngx/pull/7933))
- Fix: v2.13.0 RC1 - trigger move and rename after CustomFieldInstance saved [@shamoon](https://github.com/shamoon) ([#7927](https://github.com/paperless-ngx/paperless-ngx/pull/7927))
- Fix: v2.13.0 RC1 - increase field max lengths to accommodate larger tokens [@shamoon](https://github.com/shamoon) ([#7916](https://github.com/paperless-ngx/paperless-ngx/pull/7916))
- Fix: preserve text linebreaks in doc edit [@shamoon](https://github.com/shamoon) ([#7908](https://github.com/paperless-ngx/paperless-ngx/pull/7908))
- Fix: only show colon on cards if correspondent and title shown [@shamoon](https://github.com/shamoon) ([#7893](https://github.com/paperless-ngx/paperless-ngx/pull/7893))
- Fix: Allow ASN values of 0 from barcodes [@stumpylog](https://github.com/stumpylog) ([#7878](https://github.com/paperless-ngx/paperless-ngx/pull/7878))
- Fix: fix auto-dismiss completed tasks on open document [@shamoon](https://github.com/shamoon) ([#7869](https://github.com/paperless-ngx/paperless-ngx/pull/7869))
- Fix: trigger change warning for saved views with default fields if changed [@shamoon](https://github.com/shamoon) ([#7865](https://github.com/paperless-ngx/paperless-ngx/pull/7865))
- Fix: hidden canvas element causes scroll bug [@shamoon](https://github.com/shamoon) ([#7770](https://github.com/paperless-ngx/paperless-ngx/pull/7770))
- Fix: handle overflowing dropdowns on mobile [@shamoon](https://github.com/shamoon) ([#7758](https://github.com/paperless-ngx/paperless-ngx/pull/7758))
- Fix: chrome scrolling in >= 129 [@shamoon](https://github.com/shamoon) ([#7738](https://github.com/paperless-ngx/paperless-ngx/pull/7738))
### Maintenance
- Enhancement: use apt only when needed docker-entrypoint.sh [@gawa971](https://github.com/gawa971) ([#7756](https://github.com/paperless-ngx/paperless-ngx/pull/7756))
### Dependencies
<details>
<summary>10 changes</summary>
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.0.1 to 1.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#7830](https://github.com/paperless-ngx/paperless-ngx/pull/7830))
- Chore(deps-dev): Bump @types/node from 22.5.2 to 22.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#7829](https://github.com/paperless-ngx/paperless-ngx/pull/7829))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates [@dependabot](https://github.com/dependabot) ([#7827](https://github.com/paperless-ngx/paperless-ngx/pull/7827))
- Chore(deps-dev): Bump the frontend-jest-dependencies group in /src-ui with 2 updates [@dependabot](https://github.com/dependabot) ([#7826](https://github.com/paperless-ngx/paperless-ngx/pull/7826))
- Chore(deps-dev): Bump @playwright/test from 1.46.1 to 1.47.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#7828](https://github.com/paperless-ngx/paperless-ngx/pull/7828))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 21 updates [@dependabot](https://github.com/dependabot) ([#7825](https://github.com/paperless-ngx/paperless-ngx/pull/7825))
- Chore: Upgrades OCRMyPDF to v16 [@stumpylog](https://github.com/stumpylog) ([#7815](https://github.com/paperless-ngx/paperless-ngx/pull/7815))
- Chore: Upgrades the Docker image to use Python 3.12 [@stumpylog](https://github.com/stumpylog) ([#7796](https://github.com/paperless-ngx/paperless-ngx/pull/7796))
- Chore: Upgrade Django to 5.1 [@stumpylog](https://github.com/stumpylog) ([#7795](https://github.com/paperless-ngx/paperless-ngx/pull/7795))
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7723](https://github.com/paperless-ngx/paperless-ngx/pull/7723))
</details>
### All App Changes
<details>
<summary>43 changes</summary>
- Change: Use a TextField for the storage path field [@stumpylog](https://github.com/stumpylog) ([#7967](https://github.com/paperless-ngx/paperless-ngx/pull/7967))
- Fix: remove space before my profile button in dropdown [@tooomm](https://github.com/tooomm) ([#7963](https://github.com/paperless-ngx/paperless-ngx/pull/7963))
- Enhancement: QoL, auto-focus default select field in custom field dropdown [@shamoon](https://github.com/shamoon) ([#7961](https://github.com/paperless-ngx/paperless-ngx/pull/7961))
- Change: open not edit [@shamoon](https://github.com/shamoon) ([#7942](https://github.com/paperless-ngx/paperless-ngx/pull/7942))
- Fix: v2.13.0 RC1 - Handling of Nones when using custom fields in filepath templating [@stumpylog](https://github.com/stumpylog) ([#7933](https://github.com/paperless-ngx/paperless-ngx/pull/7933))
- Fix: v2.13.0 RC1 - trigger move and rename after CustomFieldInstance saved [@shamoon](https://github.com/shamoon) ([#7927](https://github.com/paperless-ngx/paperless-ngx/pull/7927))
- Fix: v2.13.0 RC1 - increase field max lengths to accommodate larger tokens [@shamoon](https://github.com/shamoon) ([#7916](https://github.com/paperless-ngx/paperless-ngx/pull/7916))
- Enhancement: support retain barcode split pages [@shamoon](https://github.com/shamoon) ([#7912](https://github.com/paperless-ngx/paperless-ngx/pull/7912))
- Fix: preserve text linebreaks in doc edit [@shamoon](https://github.com/shamoon) ([#7908](https://github.com/paperless-ngx/paperless-ngx/pull/7908))
- Enhancement: don't wait for doc API to load preview [@shamoon](https://github.com/shamoon) ([#7894](https://github.com/paperless-ngx/paperless-ngx/pull/7894))
- Fix: only show colon on cards if correspondent and title shown [@shamoon](https://github.com/shamoon) ([#7893](https://github.com/paperless-ngx/paperless-ngx/pull/7893))
- Feature: OAuth2 Gmail and Outlook email support [@shamoon](https://github.com/shamoon) ([#7866](https://github.com/paperless-ngx/paperless-ngx/pull/7866))
- Chore: Consolidate workflow logic [@shamoon](https://github.com/shamoon) ([#7880](https://github.com/paperless-ngx/paperless-ngx/pull/7880))
- Enhancement: live preview of storage path [@shamoon](https://github.com/shamoon) ([#7870](https://github.com/paperless-ngx/paperless-ngx/pull/7870))
- Fix: Allow ASN values of 0 from barcodes [@stumpylog](https://github.com/stumpylog) ([#7878](https://github.com/paperless-ngx/paperless-ngx/pull/7878))
- Fix: fix auto-dismiss completed tasks on open document [@shamoon](https://github.com/shamoon) ([#7869](https://github.com/paperless-ngx/paperless-ngx/pull/7869))
- Fix: trigger change warning for saved views with default fields if changed [@shamoon](https://github.com/shamoon) ([#7865](https://github.com/paperless-ngx/paperless-ngx/pull/7865))
- Feature: Enhanced templating for filename format [@stumpylog](https://github.com/stumpylog) ([#7836](https://github.com/paperless-ngx/paperless-ngx/pull/7836))
- Enhancement: management list button improvements [@shamoon](https://github.com/shamoon) ([#7848](https://github.com/paperless-ngx/paperless-ngx/pull/7848))
- Enhancement: check for mail destination directory, log post-consume errors [@mrichtarsky](https://github.com/mrichtarsky) ([#7808](https://github.com/paperless-ngx/paperless-ngx/pull/7808))
- Feature: custom fields queries [@shamoon](https://github.com/shamoon) ([#7761](https://github.com/paperless-ngx/paperless-ngx/pull/7761))
- Chore(deps-dev): Bump @codecov/webpack-plugin from 1.0.1 to 1.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#7830](https://github.com/paperless-ngx/paperless-ngx/pull/7830))
- Chore(deps-dev): Bump @types/node from 22.5.2 to 22.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#7829](https://github.com/paperless-ngx/paperless-ngx/pull/7829))
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates [@dependabot](https://github.com/dependabot) ([#7827](https://github.com/paperless-ngx/paperless-ngx/pull/7827))
- Chore(deps-dev): Bump the frontend-jest-dependencies group in /src-ui with 2 updates [@dependabot](https://github.com/dependabot) ([#7826](https://github.com/paperless-ngx/paperless-ngx/pull/7826))
- Chore(deps-dev): Bump @playwright/test from 1.46.1 to 1.47.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#7828](https://github.com/paperless-ngx/paperless-ngx/pull/7828))
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 21 updates [@dependabot](https://github.com/dependabot) ([#7825](https://github.com/paperless-ngx/paperless-ngx/pull/7825))
- Chore: Upgrades OCRMyPDF to v16 [@stumpylog](https://github.com/stumpylog) ([#7815](https://github.com/paperless-ngx/paperless-ngx/pull/7815))
- Enhancement: workflow overview toggle enable button [@shamoon](https://github.com/shamoon) ([#7818](https://github.com/paperless-ngx/paperless-ngx/pull/7818))
- Enhancement: disable-able mail rules, add toggle to overview [@shamoon](https://github.com/shamoon) ([#7810](https://github.com/paperless-ngx/paperless-ngx/pull/7810))
- Chore: Upgrades the Docker image to use Python 3.12 [@stumpylog](https://github.com/stumpylog) ([#7796](https://github.com/paperless-ngx/paperless-ngx/pull/7796))
- Chore: Upgrade Django to 5.1 [@stumpylog](https://github.com/stumpylog) ([#7795](https://github.com/paperless-ngx/paperless-ngx/pull/7795))
- Chore: Drop Python 3.9 support [@stumpylog](https://github.com/stumpylog) ([#7774](https://github.com/paperless-ngx/paperless-ngx/pull/7774))
- Feature: auto-clean some invalid pdfs [@shamoon](https://github.com/shamoon) ([#7651](https://github.com/paperless-ngx/paperless-ngx/pull/7651))
- Feature: page count [@s0llvan](https://github.com/s0llvan) ([#7750](https://github.com/paperless-ngx/paperless-ngx/pull/7750))
- Fix: hidden canvas element causes scroll bug [@shamoon](https://github.com/shamoon) ([#7770](https://github.com/paperless-ngx/paperless-ngx/pull/7770))
- Enhancement: compactify dates dropdown [@shamoon](https://github.com/shamoon) ([#7759](https://github.com/paperless-ngx/paperless-ngx/pull/7759))
- Fix: handle overflowing dropdowns on mobile [@shamoon](https://github.com/shamoon) ([#7758](https://github.com/paperless-ngx/paperless-ngx/pull/7758))
- Enhancement: set Django SESSION_EXPIRE_AT_BROWSER_CLOSE from PAPERLESS_ACCOUNT_SESSION_REMEMBER [@shamoon](https://github.com/shamoon) ([#7748](https://github.com/paperless-ngx/paperless-ngx/pull/7748))
- Enhancement: allow setting session cookie age [@shamoon](https://github.com/shamoon) ([#7743](https://github.com/paperless-ngx/paperless-ngx/pull/7743))
- Fix: chrome scrolling in >= 129 [@shamoon](https://github.com/shamoon) ([#7738](https://github.com/paperless-ngx/paperless-ngx/pull/7738))
- Feature: copy workflows and mail rules, improve layout [@shamoon](https://github.com/shamoon) ([#7727](https://github.com/paperless-ngx/paperless-ngx/pull/7727))
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7723](https://github.com/paperless-ngx/paperless-ngx/pull/7723))
</details>
## paperless-ngx 2.12.1
### Bug Fixes

View File

@@ -596,7 +596,7 @@ system. See the corresponding
: 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 nor logging in with local credentials via the API. To prevent access to the Django admin, 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).
You can optionally also automatically redirect users to the SSO login with [PAPERLESS_REDIRECT_LOGIN_TO_SSO](#PAPERLESS_REDIRECT_LOGIN_TO_SSO)
You can optionally also automatically redirect users to the SSO login with [PAPERLESS_REDIRECT_LOGIN_TO_SSO](#PAPERLESS_REDIRECT_LOGIN_TO_SSO)
Defaults to False
@@ -1217,6 +1217,10 @@ consumers working on the same file. Configure this to prevent that.
Defaults to none (thus will use [PAPERLESS_URL](#PAPERLESS_URL)).
!!! note
This setting only applies to OAuth Email setup (not to the SSO setup).
#### [`PAPERLESS_GMAIL_OAUTH_CLIENT_ID=<str>`](#PAPERLESS_GMAIL_OAUTH_CLIENT_ID) {#PAPERLESS_GMAIL_OAUTH_CLIENT_ID}
: The OAuth client ID for Gmail. This is required for Gmail OAuth Email setup. See [OAuth Email Setup](usage.md#oauth-email-setup) for more information.
@@ -1519,7 +1523,7 @@ one pod).
actual user ID on the host system, which you can get by executing
``` shell-session
$ id -u
id -u
```
Paperless will change ownership on its folders to this user, so you
@@ -1534,7 +1538,7 @@ actual user ID on the host system, which you can get by executing
actual group ID on the host system, which you can get by executing
``` shell-session
$ id -g
id -g
```
Paperless will change ownership on its folders to this group, so you

View File

@@ -69,13 +69,13 @@ first-time setup.
3. Create `consume` and `media` directories:
```bash
$ mkdir -p consume media
mkdir -p consume media
```
4. Install the Python dependencies:
```bash
$ pipenv install --dev
pipenv install --dev
```
!!! note
@@ -85,7 +85,7 @@ first-time setup.
5. Install pre-commit hooks:
```bash
$ pre-commit install
pre-commit install
```
6. Apply migrations and create a superuser for your development instance:
@@ -93,8 +93,8 @@ first-time setup.
```bash
# src/
$ python3 manage.py migrate
$ python3 manage.py createsuperuser
python3 manage.py migrate
python3 manage.py createsuperuser
```
7. You can now either ...
@@ -108,7 +108,7 @@ first-time setup.
- spin up a bare redis container
```
$ docker run -d -p 6379:6379 --restart unless-stopped redis:latest
docker run -d -p 6379:6379 --restart unless-stopped redis:latest
```
8. Continue with either back-end or front-end development or both :-).
@@ -176,7 +176,7 @@ The front end is built using AngularJS. In order to get started, you need Node.j
1. Install the Angular CLI. You might need sudo privileges to perform this command:
```bash
$ npm install -g @angular/cli
npm install -g @angular/cli
```
2. Make sure that it's on your path.
@@ -184,13 +184,13 @@ The front end is built using AngularJS. In order to get started, you need Node.j
3. Install all necessary modules:
```bash
$ npm install
npm install
```
4. You can launch a development server by running:
```bash
$ ng serve
ng serve
```
This will automatically update whenever you save. However, in-place
@@ -335,13 +335,13 @@ If you want to build the documentation locally, this is how you do it:
1. Have an active pipenv shell (`pipenv shell`) and install Python dependencies:
```bash
$ pipenv install --dev
pipenv install --dev
```
2. Build the documentation
```bash
$ mkdocs build --config-file mkdocs.yml
mkdocs build --config-file mkdocs.yml
```
_alternatively..._
@@ -352,7 +352,7 @@ If you want to build the documentation locally, this is how you do it:
something.
```bash
$ mkdocs serve
mkdocs serve
```
## Building the Docker image
@@ -450,3 +450,26 @@ def myparser_consumer_declaration(sender, **kwargs):
mime types have many extensions associated with them and the Python
methods responsible for guessing the extension do not always return
the same value.
## Using Visual Studio Code devcontainer
Another easy way to get started with development is to use Visual Studio
Code devcontainers. This approach will create a preconfigured development
environment with all of the required tools and dependencies.
[Learn more about devcontainers](https://code.visualstudio.com/docs/devcontainers/containers).
The .devcontainer/vscode/tasks.json and .devcontainer/vscode/launch.json files
contain more information about the specific tasks and launch configurations (see the
non-standard "description" field).
To get started:
1. Clone the repository on your machine and open the Paperless-ngx folder in VS Code.
2. VS Code will prompt you with "Reopen in container". Do so and wait for the environment to start.
3. Initialize the project by running the task **Project Setup: Run all Init Tasks**. This
will initialize the database tables and create a superuser. Then you can compile the front end
for production or run the frontend in debug mode.
4. The project is ready for debugging, start either run the fullstack debug or individual debug
processes. Yo spin up the project without debugging run the task **Project Start: Run all Services**

View File

@@ -2,10 +2,10 @@
You can go multiple routes to setup and run Paperless:
- [Use the easy install docker script](#docker_script)
- [Pull the image from Docker Hub](#docker_hub)
- [Use the script to setup a Docker install](#docker_script)
- [Use the Docker compose templates](#docker)
- [Build the Docker image yourself](#docker_build)
- [Install Paperless directly on your system manually (bare metal)](#bare_metal)
- [Install Paperless-ngx 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.
@@ -18,108 +18,66 @@ The bare metal route is complicated to setup but makes it easier should
you want to contribute some code back. You need to configure and run the
above mentioned components yourself.
### Docker using the Installation Script {#docker_script}
### Use the Installation Script {#docker_script}
Paperless provides an interactive installation script. This script will
ask you for a couple configuration options, download and create the
necessary configuration files, pull the docker image, start paperless
and create your user account. This script essentially performs all the
steps described in [Docker setup](#docker_hub) automatically.
Paperless provides an interactive installation script to setup a Docker Compose
installation. The script asks for a couple configuration options, and will then create the
necessary configuration files, pull the docker image, start Paperless-ngx and create your superuser
account. The script essentially automatically performs the steps described in [Docker setup](#docker).
1. Make sure that Docker and Docker Compose are installed.
!!! tip
See the Docker installation instructions at https://docs.docker.com/engine/install/
1. Make sure that Docker and Docker Compose are [installed](https://docs.docker.com/engine/install/){:target="\_blank"}.
2. Download and run the installation script:
```shell-session
$ bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"
```
!!! note
macOS users will need to install e.g. [gnu-sed](https://formulae.brew.sh/formula/gnu-sed) with support
for running as `sed`.
macOS users will need to install [gnu-sed](https://formulae.brew.sh/formula/gnu-sed) with support
for running as `sed` as well as [wget](https://formulae.brew.sh/formula/wget).
### From GHCR / Docker Hub {#docker_hub}
### Use Docker Compose {#docker}
1. Login with your user and create a folder in your home-directory to have a place for your
configuration files and consumption directory.
```shell-session
$ mkdir -v ~/paperless-ngx
```
1. Make sure that Docker and Docker Compose are [installed](https://docs.docker.com/engine/install/){:target="\_blank"}.
2. Go to the [/docker/compose directory on the project
page](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose)
and download one of the `docker-compose.*.yml` files,
depending on which database backend you want to use. Rename this
file to `docker-compose.yml`. If you want to enable
optional support for Office documents, download a file with
`-tika` in the file name. Download the
`docker-compose.env` file and the `.env` file as well and store them
in the same directory.
page](https://github.com/paperless-ngx/paperless-ngx/tree/main/docker/compose){:target="\_blank"}
and download one of the `docker-compose.*.yml` files, depending on which database backend
you want to use. Place the files in a local directory and rename it `docker-compose.yml`. Download the
`docker-compose.env` file and the `.env` file as well in the same directory.
If you want to enable optional support for Office and other documents, download a
file with `-tika` in the file name.
!!! tip
For new installations, it is recommended to use PostgreSQL as the
database backend.
3. Install [Docker](https://docs.docker.com/engine/install/) and
[Docker Compose](https://docs.docker.com/compose/install/).
!!! warning
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 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
choice. To get the latest version of Docker Compose, follow the
[Docker Compose installation guide](https://docs.docker.com/compose/install/linux/) if your package repository
doesn't include it.
4. Modify `docker-compose.yml` to your preferences. You may want to
change the path to the consumption directory. Find the line that
specifies where to mount the consumption directory:
3. Modify `docker-compose.yml` as needed. For example, you may want to change the paths to the
consumption, media etc. directories to use 'bind mounts'.
Find the line that specifies where to mount the directory, e.g.:
```yaml
- ./consume:/usr/src/paperless/consume
```
Replace the part BEFORE the colon with a local directory of your
choice:
Replace the part _before_ the colon with a local directory of your choice:
```yaml
- /home/jonaswinkler/paperless-inbox:/usr/src/paperless/consume
```
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
use from the default (8000):
```yaml
ports:
- 8000:8000
```
Replace the part BEFORE the colon with a port of your choice:
You may also want to change the default port that the webserver will
use from the default (8000) to something else, e.g. for port 8010:
```yaml
ports:
- 8010:8000
```
Don't change the part after the colon or edit other lines that
refer to port 8000. Modifying the part before the colon will map
requests on another port to the webserver running on the default
port.
**Rootless**
!!! warning
@@ -143,21 +101,16 @@ steps described in [Docker setup](#docker_hub) automatically.
> user: <user_id>
> ```
5. Modify `docker-compose.env`, following the comments in the file. The
most important change is to set `USERMAP_UID` and `USERMAP_GID` to
the uid and gid of your user on the host system. Use `id -u` and
`id -g` to get these.
4. Modify `docker-compose.env` with any configuration options you'd like.
See the [configuration documentation](configuration.md) for all options.
This ensures that both the docker container and you on the host
machine have write access to the consumption directory. If your UID
You may also need to set `USERMAP_UID` and `USERMAP_GID` to
the uid and gid of your user on the host system. Use `id -u` and
`id -g` to get these. This ensures that both the container and the host
user have write access to the consumption directory. If your UID
and GID on the host system is 1000 (the default for the first normal
user on most systems), it will work out of the box without any
modifications. `id "username"` to check.
!!! note
You can copy any setting from the file `paperless.conf.example` and
paste it here. Have a look at [configuration](configuration.md) to see what's available.
modifications. Run `id "username"` to check.
!!! note
@@ -174,33 +127,30 @@ steps described in [Docker setup](#docker_hub) automatically.
[`PAPERLESS_CONSUMER_POLLING`](configuration.md#PAPERLESS_CONSUMER_POLLING), which will disable inotify. See
[here](configuration.md#polling).
6. Run `docker compose pull`. This will pull the image.
5. Run `docker compose pull`. This will pull the image from the GitHub container registry
by default but you can change the image to pull from Docker Hub by changing the `image`
line to `image: paperlessngx/paperless-ngx:latest`.
7. To be able to login, you will need a super user. To create it,
6. To be able to login, you will need a "superuser". To create it,
execute the following command:
```shell-session
$ docker compose run --rm webserver createsuperuser
docker compose run --rm webserver createsuperuser
```
or using docker exec from within the container:
```shell-session
$ python3 manage.py createsuperuser
python3 manage.py createsuperuser
```
This will prompt you to set a username, an optional e-mail address
and finally a password (at least 8 characters).
This will guide you through the superuser setup.
8. Run `docker compose up -d`. This will create and start the necessary containers.
7. Run `docker compose up -d`. This will create and start the necessary containers.
9. The default `docker-compose.yml` exports the webserver on your local
port
8000\. If you did not change this, you should now be able to visit
your Paperless instance at `http://127.0.0.1:8000` or your servers
IP-Address:8000. Use the login credentials you have created with the
previous step.
8. Congratulations! Your Paperless-ngx instance should now be accessible at `http://127.0.0.1:8000`
(or similar, depending on your configuration). Use the superuser credentials you have
created in the previous step to login.
### Build the Docker image yourself {#docker_build}
@@ -234,11 +184,11 @@ steps described in [Docker setup](#docker_hub) automatically.
context: .
```
4. Follow steps 3 to 8 of [Docker Setup](#docker_hub). When asked to run
`docker compose pull` to pull the image, do
4. Follow the [Docker setup](#docker) above except when asked to run
`docker compose pull` to pull the image, run
```shell-session
$ docker compose build
docker compose build
```
instead to build the image.
@@ -607,8 +557,8 @@ Migration to paperless-ngx is then performed in a few simple steps:
1. Stop paperless.
```bash
$ cd /path/to/current/paperless
$ docker compose down
cd /path/to/current/paperless
docker compose down
```
2. Do a backup for two purposes: If something goes wrong, you still
@@ -632,7 +582,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
names of your volumes with
``` shell-session
$ docker volume ls | grep _data
docker volume ls | grep _data
```
and adjust the project name in the `.env` file so that it matches
@@ -643,7 +593,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
after you migrated your existing SQLite database.
5. Adjust `docker-compose.yml` and `docker-compose.env` to your needs.
See [Docker setup](#docker_hub) details on
See [Docker setup](#docker) details on
which edits are advised.
6. [Update paperless.](administration.md#updating)
@@ -653,7 +603,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
the search index:
```shell-session
$ docker compose run --rm webserver document_index reindex
docker compose run --rm webserver document_index reindex
```
This will migrate your database and create the search index. After
@@ -662,7 +612,7 @@ Migration to paperless-ngx is then performed in a few simple steps:
8. Start paperless-ngx.
```bash
$ docker compose up -d
docker compose up -d
```
This will run paperless in the background and automatically start it

View File

@@ -18,7 +18,7 @@ Check for the following issues:
automatically. Manually invoke the task processor by executing
```shell-session
$ celery --app paperless worker
celery --app paperless worker
```
- Look at the output of paperless and inspect it for any errors.
@@ -144,7 +144,7 @@ The following error occurred while consuming document.pdf: [Errno 13] Permission
This happens when paperless does not have permission to delete files
inside the consumption directory. Ensure that `USERMAP_UID` and
`USERMAP_GID` are set to the user id and group id you use on the host
operating system, if these are different from `1000`. See [Docker setup](setup.md#docker_hub).
operating system, if these are different from `1000`. See [Docker setup](setup.md#docker).
Also ensure that you are able to read and write to the consumption
directory on the host.

View File

@@ -1,9 +1,9 @@
# Usage Overview
Paperless is an application that manages your personal documents. With
the help of a document scanner (see [the scanners wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations)),
paperless transforms your unwieldy physical document binders into a searchable archive
and provides many utilities for finding and managing your documents.
Paperless-ngx is an application that manages your personal documents. With
the (optional) help of a document scanner (see [the scanners wiki](https://github.com/paperless-ngx/paperless-ngx/wiki/Scanner-&-Software-Recommendations)), Paperless-ngx transforms your unwieldy
physical documents into a searchable archive and provides many utilities
for finding and managing your documents.
## Terms and definitions
@@ -12,10 +12,10 @@ documents:
- The _consumer_ watches a specified folder and adds all documents in
that folder to paperless.
- The _web server_ provides a UI that you use to manage and search for
your scanned documents.
- The _web server_ (web UI) provides a UI that you use to manage and
search documents.
Each document has a couple of fields that you can assign to them:
Each document has data fields that you can assign to them:
- A _Document_ is a piece of paper that sometimes contains valuable
information.
@@ -41,6 +41,53 @@ Each document has a couple of fields that you can assign to them:
- The _content_ of a document is the text that was OCR'ed from the
document. This text is fed into the search engine and is used for
matching tags, correspondents and document types.
- Paperless-ngx also supports _custom fields_ which can be used to
store additional metadata about a document.
## The Web UI
The web UI is the primary way to interact with Paperless-ngx. It is a
single-page application that is built with modern web technologies and
is designed to be fast and responsive. The web UI includes a robust
interface for filtering, viewing, searching and editing documents.
You can also manage tags, correspondents, document types, and other
settings from the web UI.
The web UI also includes a 'tour' feature that can be accessed from the
settings page or from the dashboard for new users. The tour highlights
some of the key features of the web UI and can be useful for new users.
### Dashboard
The dashboard is the first page you see when you log in. By default, it
does not show any documents, but you can add saved views to the dashboard
to show documents that match certain criteria. The dashboard also includes
a button to upload documents to Paperless-ngx but you can also drag and
drop files anywhere in the app to initiate the consumption process.
### Document List
The document list is the primary way to view and interact with your documents.
You can filter the list by tags, correspondents, document types, and other
criteria. You can also edit documents in bulk including assigning tags,
correspondents, document types, and custom fields. Selecting document(s) from
the list will allow you to perform the various bulk edit operations. The
document list also includes a search bar that allows you to search for documents
by title, ASN, and use advanced search syntax.
### Document Detail
The document detail page shows all the information about a single document.
You can view the document, edit its metadata, assign tags, correspondents,
document types, and custom fields. You can also view the document history,
download the document or share it via a share link.
### Management Lists
Paperless-ngx includes management lists for tags, correspondents, document types
and more. These areas allow you to view, add, edit, delete and manage permissions
for these objects. You can also manage saved views, mail accounts, mail rules,
workflows and more from the management sections.
## Adding documents to paperless
@@ -252,7 +299,7 @@ permissions can be granted to limit access to certain parts of the UI (and corre
#### Superusers
Superusers can access all parts of the front and backend application as well as any and all objects.
Superusers can access all parts of the front and backend application as well as any and all objects. Superuser status can only be granted by another superuser.
#### Admin Status
@@ -299,6 +346,12 @@ In order to enable the password reset feature you will need to setup an SMTP bac
[`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.
### Two-factor authentication
Users can enable two-factor authentication (2FA) for their accounts from the 'My Profile' dialog. Opening the dropdown reveals a QR code that can be scanned by a 2FA app (e.g. Google Authenticator) to generate a code. The code must then be entered in the dialog to enable 2FA. If the code is accepted and 2FA is enabled, the user will be shown a set of 10 recovery codes that can be used to login in the event that the 2FA device is lost or unavailable. These codes should be stored securely and cannot be retrieved again. Once enabled, users will be required to enter a code from their 2FA app when logging in.
Should a user lose access to their 2FA device and all recovery codes, a superuser can disable 2FA for the user from the 'Users & Groups' management screen.
## Workflows
!!! note
@@ -316,6 +369,8 @@ fields and permissions, which will be merged.
### Workflow Triggers
#### Types
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
@@ -325,8 +380,10 @@ Currently, there are three events that correspond to workflow trigger 'types':
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.
4. **Scheduled**: a scheduled trigger that can be used to run workflows at a specific time. The date used can be either the document
added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date.
The following flow diagram illustrates the three trigger types:
The following flow diagram illustrates the three document trigger types:
```mermaid
flowchart TD
@@ -372,25 +429,50 @@ Workflows allow you to filter by:
### Workflow Actions
There are currently two types of workflow actions, "Assignment", which can assign:
#### Types
- Title, see [title placeholders](usage.md#title-placeholders) below
The following workflow action types are available:
##### Assignment
"Assignment" actions can assign:
- Title, see [workflow placeholders](usage.md#workflow-placeholders) below
- 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
and "Removal" actions, which can remove either all of or specific sets of the following:
##### Removal
"Removal" actions can remove either all of or specific sets of the following:
- Tags, correspondents, document types or storage paths
- Document owner
- View and / or edit permissions
- Custom fields
#### Title placeholders
##### Email
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
"Email" actions can send documents via email. This action requires a mail server to be [configured](configuration.md#email-sending). You can specify:
- The recipient email address(es) separated by commas
- The subject and body of the email, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below
- Whether to include the document as an attachment
##### Webhook
"Webhook" actions send a POST request to a specified URL. You can specify:
- The URL to send the request to
- The request body as text or as key-value pairs, which can include placeholders, see [placeholders](usage.md#workflow-placeholders) below.
- Encoding for the request body, either JSON or form data
- The request headers as key-value pairs
#### Workflow placeholders
Some workflow text 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 text 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
@@ -405,6 +487,7 @@ applied. You can use the following placeholders with any trigger type:
- `{added_day}`: added day
- `{added_time}`: added time in HH:MM format
- `{original_filename}`: original file name without extension
- `{filename}`: current file name without extension
The following placeholders are only available for "added" or "updated" triggers
@@ -416,6 +499,7 @@ The following placeholders are only available for "added" or "updated" triggers
- `{created_month_name_short}`: created month short name
- `{created_day}`: created day
- `{created_time}`: created time in HH:MM format
- `{doc_url}`: URL to the document in the web UI. Requires the `PAPERLESS_URL` setting to be set.
### Workflow permissions
@@ -752,8 +836,8 @@ Paperless-ngx consists of the following components:
with paperless. You may start the webserver directly with
```shell-session
$ cd /path/to/paperless/src/
$ gunicorn -c ../gunicorn.conf.py paperless.wsgi
cd /path/to/paperless/src/
gunicorn -c ../gunicorn.conf.py paperless.wsgi
```
or by any other means such as Apache `mod_wsgi`.
@@ -768,8 +852,8 @@ Paperless-ngx consists of the following components:
Start the consumer with the management command `document_consumer`:
```shell-session
$ cd /path/to/paperless/src/
$ python3 manage.py document_consumer
cd /path/to/paperless/src/
python3 manage.py document_consumer
```
- **The task processor:** Paperless relies on [Celery - Distributed

View File

@@ -3,7 +3,7 @@ import os
# See https://docs.gunicorn.org/en/stable/settings.html for
# explanations of settings
bind = f'{os.getenv("PAPERLESS_BIND_ADDR", "[::]")}:{os.getenv("PAPERLESS_PORT", 8000)}'
bind = f"{os.getenv('PAPERLESS_BIND_ADDR', '[::]')}:{os.getenv('PAPERLESS_PORT', 8000)}"
workers = int(os.getenv("PAPERLESS_WEBSERVER_WORKERS", 1))
worker_class = "paperless.workers.ConfigurableWorker"

View File

@@ -330,8 +330,13 @@ SECRET_KEY=$(LC_ALL=C tr -dc 'a-zA-Z0-9!#$%&()*+,-./:;<=>?@[\]^_`{|}~' < /dev/ur
DEFAULT_LANGUAGES=("deu eng fra ita spa")
_split_langs="${OCR_LANGUAGE//+/ }"
read -r -a OCR_LANGUAGES_ARRAY <<< "${_split_langs}"
# OCR_LANG requires underscores, replace dashes if the user gave them with underscores
readonly ocr_langs=${OCR_LANGUAGE//-/_}
# OCR_LANGS (the install version) uses dashes, not underscores, so convert underscore to dash and plus to space
install_langs=${OCR_LANGUAGE//_/-} # First convert any underscores to dashes
install_langs=${install_langs//+/ } # Then convert plus signs to spaces
read -r -a install_langs_array <<< "${install_langs}"
{
if [[ ! $URL == "" ]] ; then
@@ -344,10 +349,10 @@ read -r -a OCR_LANGUAGES_ARRAY <<< "${_split_langs}"
echo "USERMAP_GID=$USERMAP_GID"
fi
echo "PAPERLESS_TIME_ZONE=$TIME_ZONE"
echo "PAPERLESS_OCR_LANGUAGE=$OCR_LANGUAGE"
echo "PAPERLESS_OCR_LANGUAGE=$ocr_langs"
echo "PAPERLESS_SECRET_KEY='$SECRET_KEY'"
if [[ ! ${DEFAULT_LANGUAGES[*]} =~ ${OCR_LANGUAGES_ARRAY[*]} ]] ; then
echo "PAPERLESS_OCR_LANGUAGES=${OCR_LANGUAGES_ARRAY[*]}"
if [[ ! ${DEFAULT_LANGUAGES[*]} =~ ${install_langs_array[*]} ]] ; then
echo "PAPERLESS_OCR_LANGUAGES=${install_langs_array[*]}"
fi
} > docker-compose.env

View File

@@ -11,8 +11,7 @@
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
"tsconfig.json"
],
"createDefaultProgram": true
},

View File

@@ -81,7 +81,8 @@
"scripts": [],
"allowedCommonJsDependencies": [
"ng2-pdf-viewer",
"file-saver"
"file-saver",
"utif"
],
"vendorChunk": true,
"extractLicenses": false,

View File

@@ -1,6 +1,7 @@
import { test, expect } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'node:path'
const REQUESTS_HAR = 'e2e/admin/requests/api-settings.har'
const REQUESTS_HAR = path.join(__dirname, 'requests/api-settings.har')
test('should activate / deactivate save button when settings change', async ({
page,
@@ -33,24 +34,3 @@ test('should apply appearance changes when set', async ({ page }) => {
await page.getByLabel('Enable dark mode').click()
await expect(page.locator('html')).toHaveAttribute('data-bs-theme', /dark/)
})
test('should toggle saved view options when set & saved', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })
await page.goto('/settings/savedviews')
await page.getByLabel('Show on dashboard').first().click()
await page.getByLabel('Show in sidebar').first().click()
const updatePromise = page.waitForRequest((request) => {
if (!request.url().includes('8')) return true // skip other saved views
const data = request.postDataJSON()
const isValid =
data['show_on_dashboard'] === true && data['show_in_sidebar'] === true
return (
isValid &&
request.method() === 'PATCH' &&
request.url().includes('/api/saved_views/')
)
})
await page.getByRole('button', { name: 'Save' }).scrollIntoViewIfNeeded()
await page.getByRole('button', { name: 'Save' }).click()
await updatePromise
})

View File

@@ -1,9 +1,10 @@
import { test, expect } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'node:path'
const REQUESTS_HAR1 = 'e2e/dashboard/requests/api-dashboard1.har'
const REQUESTS_HAR2 = 'e2e/dashboard/requests/api-dashboard2.har'
const REQUESTS_HAR3 = 'e2e/dashboard/requests/api-dashboard3.har'
const REQUESTS_HAR4 = 'e2e/dashboard/requests/api-dashboard4.har'
const REQUESTS_HAR1 = path.join(__dirname, 'requests/api-dashboard1.har')
const REQUESTS_HAR2 = path.join(__dirname, 'requests/api-dashboard2.har')
const REQUESTS_HAR3 = path.join(__dirname, 'requests/api-dashboard3.har')
const REQUESTS_HAR4 = path.join(__dirname, 'requests/api-dashboard4.har')
test('dashboard inbox link', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR1, { notFound: 'fallback' })

View File

@@ -1,7 +1,8 @@
import { test, expect } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'node:path'
const REQUESTS_HAR = 'e2e/document-detail/requests/api-document-detail.har'
const REQUESTS_HAR2 = 'e2e/document-detail/requests/api-document-detail2.har'
const REQUESTS_HAR = path.join(__dirname, 'requests/api-document-detail.har')
const REQUESTS_HAR2 = path.join(__dirname, 'requests/api-document-detail2.har')
test('should activate / deactivate save button when changes are saved', async ({
page,

View File

@@ -1,11 +1,12 @@
import { test, expect } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'node:path'
const REQUESTS_HAR1 = 'e2e/document-list/requests/api-document-list1.har'
const REQUESTS_HAR2 = 'e2e/document-list/requests/api-document-list2.har'
const REQUESTS_HAR3 = 'e2e/document-list/requests/api-document-list3.har'
const REQUESTS_HAR4 = 'e2e/document-list/requests/api-document-list4.har'
const REQUESTS_HAR5 = 'e2e/document-list/requests/api-document-list5.har'
const REQUESTS_HAR6 = 'e2e/document-list/requests/api-document-list6.har'
const REQUESTS_HAR1 = path.join(__dirname, 'requests/api-document-list1.har')
const REQUESTS_HAR2 = path.join(__dirname, 'requests/api-document-list2.har')
const REQUESTS_HAR3 = path.join(__dirname, 'requests/api-document-list3.har')
const REQUESTS_HAR4 = path.join(__dirname, 'requests/api-document-list4.har')
const REQUESTS_HAR5 = path.join(__dirname, 'requests/api-document-list5.har')
const REQUESTS_HAR6 = path.join(__dirname, 'requests/api-document-list6.har')
test('basic filtering', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR1, { notFound: 'fallback' })
@@ -134,11 +135,11 @@ test('sorting', async ({ page }) => {
test('change views', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR5, { notFound: 'fallback' })
await page.goto('/documents')
await page.locator('.btn-group label').first().click()
await page.locator('.btn-group > label').first().click()
await expect(page.locator('pngx-document-list table')).toBeVisible()
await page.locator('.btn-group label').nth(1).click()
await page.locator('label:nth-child(4)').first().click()
await expect(page.locator('pngx-document-card-small').first()).toBeAttached()
await page.locator('.btn-group label').nth(2).click()
await page.locator('label:nth-child(6)').click()
await expect(page.locator('pngx-document-card-large').first()).toBeAttached()
})

View File

@@ -1,6 +1,7 @@
import { test, expect } from '@playwright/test'
import { expect, test } from '@playwright/test'
import path from 'node:path'
const REQUESTS_HAR = 'e2e/permissions/requests/api-global-permissions.har'
const REQUESTS_HAR = path.join(__dirname, 'requests/api-global-permissions.har')
test('should not allow user to edit settings', async ({ page }) => {
await page.routeFromHAR(REQUESTS_HAR, { notFound: 'fallback' })

View File

@@ -1,8 +1,8 @@
import * as webpack from 'webpack'
import {
CustomWebpackBrowserSchema,
TargetOptions,
} from '@angular-builders/custom-webpack'
import * as webpack from 'webpack'
const { codecovWebpackPlugin } = require('@codecov/webpack-plugin')
export default (

File diff suppressed because it is too large Load Diff

9341
src-ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,60 +11,63 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "^18.2.6",
"@angular/common": "~18.2.6",
"@angular/compiler": "~18.2.6",
"@angular/core": "~18.2.6",
"@angular/forms": "~18.2.6",
"@angular/localize": "~18.2.6",
"@angular/platform-browser": "~18.2.6",
"@angular/platform-browser-dynamic": "~18.2.6",
"@angular/router": "~18.2.6",
"@ng-bootstrap/ng-bootstrap": "^17.0.1",
"@ng-select/ng-select": "^13.9.0",
"@angular/cdk": "^19.0.2",
"@angular/common": "~19.0.3",
"@angular/compiler": "~19.0.3",
"@angular/core": "~19.0.3",
"@angular/forms": "~19.0.3",
"@angular/localize": "~19.0.3",
"@angular/platform-browser": "~19.0.3",
"@angular/platform-browser-dynamic": "~19.0.3",
"@angular/router": "~19.0.3",
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
"@ng-select/ng-select": "^14.1.0",
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3",
"file-saver": "^2.0.5",
"mime-names": "^1.0.0",
"ng2-pdf-viewer": "^10.3.1",
"ng2-pdf-viewer": "^10.4.0",
"ngx-bootstrap-icons": "^1.9.3",
"ngx-color": "^9.0.0",
"ngx-cookie-service": "^18.0.0",
"ngx-cookie-service": "^19.0.0",
"ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^15.0.0",
"ngx-ui-tour-ng-bootstrap": "^16.0.0",
"rxjs": "^7.8.1",
"tslib": "^2.7.0",
"uuid": "^10.0.0",
"zone.js": "^0.14.8"
"tslib": "^2.8.1",
"utif": "^3.1.0",
"uuid": "^11.0.2",
"zone.js": "^0.15.0"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^18.0.0",
"@angular-builders/jest": "^18.0.0",
"@angular-devkit/build-angular": "^18.2.2",
"@angular-devkit/core": "^18.2.6",
"@angular-devkit/schematics": "^18.2.6",
"@angular-eslint/builder": "18.3.1",
"@angular-eslint/eslint-plugin": "18.3.1",
"@angular-eslint/eslint-plugin-template": "18.3.1",
"@angular-eslint/schematics": "18.3.1",
"@angular-eslint/template-parser": "18.3.1",
"@angular/cli": "~18.2.6",
"@angular/compiler-cli": "~18.2.2",
"@codecov/webpack-plugin": "^1.2.0",
"@playwright/test": "^1.47.2",
"@types/jest": "^29.5.13",
"@types/node": "^22.7.4",
"@typescript-eslint/eslint-plugin": "^8.8.0",
"@typescript-eslint/parser": "^8.8.0",
"@angular-builders/custom-webpack": "^19.0.0-beta.0",
"@angular-builders/jest": "^19.0.0-beta.1",
"@angular-devkit/build-angular": "^19.0.4",
"@angular-devkit/core": "^19.0.4",
"@angular-devkit/schematics": "^19.0.4",
"@angular-eslint/builder": "19.0.0",
"@angular-eslint/eslint-plugin": "19.0.0",
"@angular-eslint/eslint-plugin-template": "19.0.0",
"@angular-eslint/schematics": "19.0.0",
"@angular-eslint/template-parser": "19.0.0",
"@angular/cli": "~19.0.4",
"@angular/compiler-cli": "~19.0.3",
"@codecov/webpack-plugin": "^1.2.1",
"@playwright/test": "^1.48.2",
"@types/jest": "^29.5.14",
"@types/node": "^22.8.6",
"@typescript-eslint/eslint-plugin": "^8.12.2",
"@typescript-eslint/parser": "^8.12.2",
"@typescript-eslint/utils": "^8.0.0",
"eslint": "^9.11.1",
"eslint": "^9.14.0",
"jest": "29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-preset-angular": "^14.2.4",
"jest-preset-angular": "^14.4.2",
"jest-websocket-mock": "^2.5.0",
"patch-package": "^8.0.0",
"prettier-plugin-organize-imports": "^4.1.0",
"ts-node": "~10.9.1",
"typescript": "^5.5.4"
}
},
"typings": "./src/typings.d.ts"
}

View File

@@ -1,9 +1,10 @@
import { jest } from '@jest/globals'
if (process.env.NODE_ENV === 'test') {
require('jest-preset-angular/setup-jest')
}
import '@angular/localize/init'
import { TextEncoder, TextDecoder } from 'util'
import { jest } from '@jest/globals'
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone'
import { TextDecoder, TextEncoder } from 'util'
if (process.env.NODE_ENV === 'test') {
setupZoneTestEnv()
}
global.TextEncoder = TextEncoder
global.TextDecoder = TextDecoder

View File

@@ -1,32 +1,33 @@
import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'
import { RouterModule, Routes } from '@angular/router'
import { ConfigComponent } from './components/admin/config/config.component'
import { LogsComponent } from './components/admin/logs/logs.component'
import { SettingsComponent } from './components/admin/settings/settings.component'
import { TasksComponent } from './components/admin/tasks/tasks.component'
import { TrashComponent } from './components/admin/trash/trash.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'
import { DashboardComponent } from './components/dashboard/dashboard.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
import { DocumentListComponent } from './components/document-list/document-list.component'
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
import { LogsComponent } from './components/admin/logs/logs.component'
import { SettingsComponent } from './components/admin/settings/settings.component'
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
import { NotFoundComponent } from './components/not-found/not-found.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DirtyFormGuard } from './guards/dirty-form.guard'
import { MailComponent } from './components/manage/mail/mail.component'
import { SavedViewsComponent } from './components/manage/saved-views/saved-views.component'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { TasksComponent } from './components/admin/tasks/tasks.component'
import { PermissionsGuard } from './guards/permissions.guard'
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
import { WorkflowsComponent } from './components/manage/workflows/workflows.component'
import { NotFoundComponent } from './components/not-found/not-found.component'
import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { DirtyFormGuard } from './guards/dirty-form.guard'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { PermissionsGuard } from './guards/permissions.guard'
import {
PermissionAction,
PermissionType,
} from './services/permissions.service'
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'
import { TrashComponent } from './components/admin/trash/trash.component'
export const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
@@ -165,6 +166,10 @@ export const routes: Routes = [
path: 'settings/usersgroups',
redirectTo: '/usersgroups',
},
{
path: 'settings/savedviews',
redirectTo: '/savedviews',
},
{
path: 'settings',
component: SettingsComponent,
@@ -255,6 +260,17 @@ export const routes: Routes = [
},
},
},
{
path: 'savedviews',
component: SavedViewsComponent,
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.SavedView,
},
},
},
],
},

View File

@@ -1,30 +1,31 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
TestBed,
tick,
} from '@angular/core/testing'
import { Router, RouterModule } from '@angular/router'
import { TourService, TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { NgxFileDropModule } from 'ngx-file-drop'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { Subject } from 'rxjs'
import { routes } from './app-routing.module'
import { AppComponent } from './app.component'
import { ToastsComponent } from './components/common/toasts/toasts.component'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { PermissionsGuard } from './guards/permissions.guard'
import {
ConsumerStatusService,
FileStatus,
} from './services/consumer-status.service'
import { PermissionsService } from './services/permissions.service'
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'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { PermissionsService } from './services/permissions.service'
import { SettingsService } from './services/settings.service'
import { Toast, ToastService } from './services/toast.service'
describe('AppComponent', () => {
let component: AppComponent
@@ -39,12 +40,15 @@ describe('AppComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [AppComponent, ToastsComponent, FileDropComponent],
imports: [
TourNgBootstrapModule,
RouterModule.forRoot(routes),
NgxFileDropModule,
NgbModalModule,
AppComponent,
ToastsComponent,
FileDropComponent,
NgxBootstrapIconsModule.pick(allIcons),
],
providers: [
PermissionsGuard,

View File

@@ -1,23 +1,31 @@
import { SettingsService } from './services/settings.service'
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'
import { Router, RouterOutlet } from '@angular/router'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { first, Subscription } from 'rxjs'
import { ToastsComponent } from './components/common/toasts/toasts.component'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { SETTINGS_KEYS } from './data/ui-settings'
import { ConsumerStatusService } from './services/consumer-status.service'
import { ToastService } from './services/toast.service'
import { TasksService } from './services/tasks.service'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import { HotKeyService } from './services/hot-key.service'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from './services/permissions.service'
import { HotKeyService } from './services/hot-key.service'
import { SettingsService } from './services/settings.service'
import { TasksService } from './services/tasks.service'
import { ToastService } from './services/toast.service'
@Component({
selector: 'pngx-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
imports: [
FileDropComponent,
ToastsComponent,
TourNgBootstrapModule,
RouterOutlet,
],
})
export class AppComponent implements OnInit, OnDestroy {
newDocumentSubscription: Subscription
@@ -165,7 +173,7 @@ export class AppComponent implements OnInit, OnDestroy {
[
{
anchorId: 'tour.dashboard',
content: $localize`The dashboard can be used to show saved views, such as an 'Inbox'. Those settings are found under Settings > Saved Views once you have created some.`,
content: $localize`The dashboard can be used to show saved views, such as an 'Inbox'. Views are found under Manage > Saved Views once you have created some.`,
route: '/dashboard',
delayAfterNavigation: 500,
isOptional: false,
@@ -227,7 +235,7 @@ export class AppComponent implements OnInit, OnDestroy {
},
{
anchorId: 'tour.settings',
content: $localize`Check out the settings for various tweaks to the web app and toggle settings for saved views.`,
content: $localize`Check out the settings for various tweaks to the web app.`,
route: '/settings',
backdropConfig: {
offset: 0,

View File

@@ -1,571 +0,0 @@
import { BrowserModule } from '@angular/platform-browser'
import { APP_INITIALIZER, NgModule } from '@angular/core'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'
import {
NgbDateAdapter,
NgbDateParserFormatter,
NgbModule,
} from '@ng-bootstrap/ng-bootstrap'
import {
HTTP_INTERCEPTORS,
provideHttpClient,
withInterceptorsFromDi,
} from '@angular/common/http'
import { DocumentListComponent } from './components/document-list/document-list.component'
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
import { DashboardComponent } from './components/dashboard/dashboard.component'
import { TagListComponent } from './components/manage/tag-list/tag-list.component'
import { DocumentTypeListComponent } from './components/manage/document-type-list/document-type-list.component'
import { CorrespondentListComponent } from './components/manage/correspondent-list/correspondent-list.component'
import { LogsComponent } from './components/admin/logs/logs.component'
import { SettingsComponent } from './components/admin/settings/settings.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { DatePipe, registerLocaleData } from '@angular/common'
import { NotFoundComponent } from './components/not-found/not-found.component'
import { ConfirmDialogComponent } from './components/common/confirm-dialog/confirm-dialog.component'
import { CorrespondentEditDialogComponent } from './components/common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { TagComponent } from './components/common/tag/tag.component'
import { ClearableBadgeComponent } from './components/common/clearable-badge/clearable-badge.component'
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'
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 { 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'
import { NgxFileDropModule } from 'ngx-file-drop'
import { TextComponent } from './components/common/input/text/text.component'
import { TextAreaComponent } from './components/common/input/textarea/textarea.component'
import { SelectComponent } from './components/common/input/select/select.component'
import { CheckComponent } from './components/common/input/check/check.component'
import { UrlComponent } from './components/common/input/url/url.component'
import { PasswordComponent } from './components/common/input/password/password.component'
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component'
import { TagsComponent } from './components/common/input/tags/tags.component'
import { IfPermissionsDirective } from './directives/if-permissions.directive'
import { SortableDirective } from './directives/sortable.directive'
import { CookieService } from 'ngx-cookie-service'
import { CsrfInterceptor } from './interceptors/csrf.interceptor'
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component'
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component'
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component'
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component'
import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component'
import { YesNoPipe } from './pipes/yes-no.pipe'
import { FileSizePipe } from './pipes/file-size.pipe'
import { FilterPipe } from './pipes/filter.pipe'
import { DocumentTitlePipe } from './pipes/document-title.pipe'
import { MetadataCollapseComponent } from './components/document-detail/metadata-collapse/metadata-collapse.component'
import { SelectDialogComponent } from './components/common/select-dialog/select-dialog.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { NumberComponent } from './components/common/input/number/number.component'
import { SafeUrlPipe } from './pipes/safeurl.pipe'
import { SafeHtmlPipe } from './pipes/safehtml.pipe'
import { CustomDatePipe } from './pipes/custom-date.pipe'
import { DateComponent } from './components/common/input/date/date.component'
import { ISODateAdapter } from './utils/ngb-iso-date-adapter'
import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor'
import { ColorSliderModule } from 'ngx-color/slider'
import { ColorComponent } from './components/common/input/color/color.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentNotesComponent } from './components/document-notes/document-notes.component'
import { PermissionsGuard } from './guards/permissions.guard'
import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { StoragePathEditDialogComponent } from './components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SettingsService } from './services/settings.service'
import { TasksComponent } from './components/admin/tasks/tasks.component'
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
import { UserEditDialogComponent } from './components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { GroupEditDialogComponent } from './components/common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
import { PermissionsSelectComponent } from './components/common/permissions-select/permissions-select.component'
import { MailAccountEditDialogComponent } from './components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component'
import { MailRuleEditDialogComponent } from './components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component'
import { PermissionsUserComponent } from './components/common/input/permissions/permissions-user/permissions-user.component'
import { PermissionsGroupComponent } from './components/common/input/permissions/permissions-group/permissions-group.component'
import { IfOwnerDirective } from './directives/if-owner.directive'
import { IfObjectPermissionsDirective } from './directives/if-object-permissions.directive'
import { PermissionsDialogComponent } from './components/common/permissions-dialog/permissions-dialog.component'
import { PermissionsFormComponent } from './components/common/input/permissions/permissions-form/permissions-form.component'
import { PermissionsFilterDropdownComponent } from './components/common/permissions-filter-dropdown/permissions-filter-dropdown.component'
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 { 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'
import { FileDropComponent } from './components/file-drop/file-drop.component'
import { CustomFieldsComponent } from './components/manage/custom-fields/custom-fields.component'
import { CustomFieldEditDialogComponent } from './components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { CustomFieldsDropdownComponent } from './components/common/custom-fields-dropdown/custom-fields-dropdown.component'
import { CustomFieldsQueryDropdownComponent } from './components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component'
import { ProfileEditDialogComponent } from './components/common/profile-edit-dialog/profile-edit-dialog.component'
import { PdfViewerModule } from 'ng2-pdf-viewer'
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 { 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 { DeletePagesConfirmDialogComponent } from './components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component'
import { TrashComponent } from './components/admin/trash/trash.component'
import {
airplane,
archive,
arrowClockwise,
arrowCounterclockwise,
arrowDown,
arrowLeft,
arrowRepeat,
arrowRight,
arrowRightShort,
arrowUpRight,
asterisk,
braces,
bodyText,
boxArrowInRight,
boxArrowUp,
boxArrowUpRight,
boxes,
calendar,
calendarEvent,
calendarEventFill,
cardChecklist,
cardHeading,
caretDown,
caretUp,
chatLeftText,
check,
check2All,
checkAll,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
clipboard,
clipboardCheck,
clipboardCheckFill,
clipboardFill,
dash,
dashCircle,
diagram3,
dice5,
doorOpen,
download,
envelope,
envelopeAt,
envelopeAtFill,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkCheck,
fileEarmarkFill,
fileEarmarkLock,
fileEarmarkMinus,
files,
fileText,
filter,
folder,
folderFill,
funnel,
gear,
google,
grid,
gripVertical,
hash,
hddStack,
house,
infoCircle,
journals,
link,
listTask,
listUl,
microsoft,
nodePlus,
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,
xCircle,
xLg,
} from 'ngx-bootstrap-icons'
const icons = {
airplane,
archive,
arrowClockwise,
arrowCounterclockwise,
arrowDown,
arrowLeft,
arrowRepeat,
arrowRight,
arrowRightShort,
arrowUpRight,
asterisk,
braces,
bodyText,
boxArrowInRight,
boxArrowUp,
boxArrowUpRight,
boxes,
calendar,
calendarEvent,
calendarEventFill,
cardChecklist,
cardHeading,
caretDown,
caretUp,
chatLeftText,
check,
check2All,
checkAll,
checkCircleFill,
checkLg,
chevronDoubleLeft,
chevronDoubleRight,
clipboard,
clipboardCheck,
clipboardCheckFill,
clipboardFill,
dash,
dashCircle,
diagram3,
dice5,
doorOpen,
download,
envelope,
envelopeAt,
envelopeAtFill,
exclamationCircleFill,
exclamationTriangle,
exclamationTriangleFill,
eye,
fileEarmark,
fileEarmarkCheck,
fileEarmarkFill,
fileEarmarkLock,
fileEarmarkMinus,
files,
fileText,
filter,
folder,
folderFill,
funnel,
gear,
google,
grid,
gripVertical,
hash,
hddStack,
house,
infoCircle,
journals,
link,
listTask,
listUl,
microsoft,
nodePlus,
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,
xCircle,
xLg,
}
import localeAf from '@angular/common/locales/af'
import localeAr from '@angular/common/locales/ar'
import localeBe from '@angular/common/locales/be'
import localeBg from '@angular/common/locales/bg'
import localeCa from '@angular/common/locales/ca'
import localeCs from '@angular/common/locales/cs'
import localeDa from '@angular/common/locales/da'
import localeDe from '@angular/common/locales/de'
import localeEl from '@angular/common/locales/el'
import localeEnGb from '@angular/common/locales/en-GB'
import localeEs from '@angular/common/locales/es'
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 localeKo from '@angular/common/locales/ko'
import localeLb from '@angular/common/locales/lb'
import localeNl from '@angular/common/locales/nl'
import localeNo from '@angular/common/locales/no'
import localePl from '@angular/common/locales/pl'
import localePt from '@angular/common/locales/pt'
import localeRo from '@angular/common/locales/ro'
import localeRu from '@angular/common/locales/ru'
import localeSk from '@angular/common/locales/sk'
import localeSl from '@angular/common/locales/sl'
import localeSr from '@angular/common/locales/sr'
import localeSv from '@angular/common/locales/sv'
import localeTr from '@angular/common/locales/tr'
import localeUk from '@angular/common/locales/uk'
import localeZh from '@angular/common/locales/zh'
registerLocaleData(localeAf)
registerLocaleData(localeAr)
registerLocaleData(localeBe)
registerLocaleData(localeBg)
registerLocaleData(localeCa)
registerLocaleData(localeCs)
registerLocaleData(localeDa)
registerLocaleData(localeDe)
registerLocaleData(localeEl)
registerLocaleData(localeEnGb)
registerLocaleData(localeEs)
registerLocaleData(localeFi)
registerLocaleData(localeFr)
registerLocaleData(localeHu)
registerLocaleData(localeIt)
registerLocaleData(localeJa)
registerLocaleData(localeKo)
registerLocaleData(localeLb)
registerLocaleData(localeNl)
registerLocaleData(localeNo)
registerLocaleData(localePl)
registerLocaleData(localePt, 'pt-BR')
registerLocaleData(localePt, 'pt-PT')
registerLocaleData(localeRo)
registerLocaleData(localeRu)
registerLocaleData(localeSk)
registerLocaleData(localeSl)
registerLocaleData(localeSr)
registerLocaleData(localeSv)
registerLocaleData(localeTr)
registerLocaleData(localeUk)
registerLocaleData(localeZh)
function initializeApp(settings: SettingsService) {
return () => {
return settings.initializeSettings()
}
}
@NgModule({
declarations: [
AppComponent,
DocumentListComponent,
DocumentDetailComponent,
DashboardComponent,
TagListComponent,
DocumentTypeListComponent,
CorrespondentListComponent,
StoragePathListComponent,
LogsComponent,
SettingsComponent,
NotFoundComponent,
CorrespondentEditDialogComponent,
ConfirmDialogComponent,
TagEditDialogComponent,
DocumentTypeEditDialogComponent,
StoragePathEditDialogComponent,
TagComponent,
ClearableBadgeComponent,
PageHeaderComponent,
AppFrameComponent,
ToastsComponent,
FilterEditorComponent,
FilterableDropdownComponent,
ToggleableDropdownButtonComponent,
DatesDropdownComponent,
DocumentCardLargeComponent,
DocumentCardSmallComponent,
BulkEditorComponent,
TextComponent,
TextAreaComponent,
SelectComponent,
CheckComponent,
UrlComponent,
PasswordComponent,
SaveViewConfigDialogComponent,
TagsComponent,
IfPermissionsDirective,
SortableDirective,
SavedViewWidgetComponent,
StatisticsWidgetComponent,
UploadFileWidgetComponent,
WidgetFrameComponent,
WelcomeWidgetComponent,
YesNoPipe,
FileSizePipe,
FilterPipe,
DocumentTitlePipe,
MetadataCollapseComponent,
SelectDialogComponent,
NumberComponent,
SafeUrlPipe,
SafeHtmlPipe,
CustomDatePipe,
DateComponent,
ColorComponent,
DocumentAsnComponent,
DocumentNotesComponent,
TasksComponent,
UserEditDialogComponent,
GroupEditDialogComponent,
PermissionsSelectComponent,
MailAccountEditDialogComponent,
MailRuleEditDialogComponent,
PermissionsUserComponent,
PermissionsGroupComponent,
IfOwnerDirective,
IfObjectPermissionsDirective,
PermissionsDialogComponent,
PermissionsFormComponent,
PermissionsFilterDropdownComponent,
UsernamePipe,
LogoComponent,
IsNumberPipe,
ShareLinksDropdownComponent,
WorkflowsComponent,
WorkflowEditDialogComponent,
MailComponent,
UsersAndGroupsComponent,
FileDropComponent,
CustomFieldsComponent,
CustomFieldEditDialogComponent,
CustomFieldsDropdownComponent,
CustomFieldsQueryDropdownComponent,
ProfileEditDialogComponent,
DocumentLinkComponent,
PreviewPopupComponent,
SwitchComponent,
ConfigComponent,
FileComponent,
ConfirmButtonComponent,
MonetaryComponent,
SystemStatusDialogComponent,
RotateConfirmDialogComponent,
MergeConfirmDialogComponent,
SplitConfirmDialogComponent,
DocumentHistoryComponent,
DragDropSelectComponent,
CustomFieldDisplayComponent,
GlobalSearchComponent,
HotkeyDialogComponent,
DeletePagesConfirmDialogComponent,
TrashComponent,
],
bootstrap: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
NgbModule,
FormsModule,
ReactiveFormsModule,
PdfViewerModule,
NgxFileDropModule,
NgSelectModule,
ColorSliderModule,
TourNgBootstrapModule,
DragDropModule,
NgxBootstrapIconsModule.pick(icons),
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeApp,
deps: [SettingsService],
multi: true,
},
DatePipe,
CookieService,
{
provide: HTTP_INTERCEPTORS,
useClass: CsrfInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: ApiVersionInterceptor,
multi: true,
},
FilterPipe,
DocumentTitlePipe,
{ provide: NgbDateAdapter, useClass: ISODateAdapter },
{ provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
PermissionsGuard,
DirtyDocGuard,
DirtySavedViewGuard,
UsernamePipe,
provideHttpClient(withInterceptorsFromDi()),
],
})
export class AppModule {}

View File

@@ -1,24 +1,24 @@
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 { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
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'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { of, throwError } from 'rxjs'
import { OutputTypeConfig } from 'src/app/data/paperless-config'
import { ConfigService } from 'src/app/services/config.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { FileComponent } from '../../common/input/file/file.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { SwitchComponent } from '../../common/input/switch/switch.component'
import { TextComponent } from '../../common/input/text/text.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ConfigComponent } from './config.component'
describe('ConfigComponent', () => {
let component: ConfigComponent
@@ -29,15 +29,6 @@ describe('ConfigComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
ConfigComponent,
TextComponent,
SelectComponent,
NumberComponent,
SwitchComponent,
FileComponent,
PageHeaderComponent,
],
imports: [
BrowserModule,
NgbModule,
@@ -45,6 +36,13 @@ describe('ConfigComponent', () => {
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule.pick(allIcons),
ConfigComponent,
TextComponent,
SelectComponent,
NumberComponent,
SwitchComponent,
FileComponent,
PageHeaderComponent,
],
providers: [
provideHttpClient(withInterceptorsFromDi()),

View File

@@ -1,33 +1,60 @@
import { AsyncPipe } from '@angular/common'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { AbstractControl, FormControl, FormGroup } from '@angular/forms'
import {
AbstractControl,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
} from '@angular/forms'
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import {
BehaviorSubject,
Observable,
Subject,
Subscription,
first,
takeUntil,
} from 'rxjs'
import {
PaperlessConfigOptions,
ConfigCategory,
ConfigOption,
ConfigOptionType,
PaperlessConfig,
PaperlessConfigOptions,
} 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'
import { ToastService } from 'src/app/services/toast.service'
import { FileComponent } from '../../common/input/file/file.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { SwitchComponent } from '../../common/input/switch/switch.component'
import { TextComponent } from '../../common/input/text/text.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({
selector: 'pngx-config',
templateUrl: './config.component.html',
styleUrl: './config.component.scss',
imports: [
PageHeaderComponent,
SelectComponent,
SwitchComponent,
TextComponent,
NumberComponent,
FileComponent,
AsyncPipe,
NgbNavModule,
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule,
],
})
export class ConfigComponent
extends ComponentWithPermissions
extends LoadingComponentWithPermissions
implements OnInit, OnDestroy, DirtyComponent
{
public readonly ConfigOptionType = ConfigOptionType
@@ -45,15 +72,11 @@ export class ConfigComponent
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,
@@ -67,7 +90,6 @@ export class ConfigComponent
}
ngOnInit(): void {
this.loading = true
this.configService
.getConfig()
.pipe(takeUntil(this.unsubscribeNotifier))

View File

@@ -4,7 +4,7 @@
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">
<input class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div>
</pngx-page-header>
@@ -17,7 +17,7 @@
</a>
</li>
}
@if (isLoading || !logFiles.length) {
@if (loading || !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) {
@@ -30,15 +30,13 @@
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<div class="bg-dark p-3 text-light font-monospace log-container" #logContainer>
@if (isLoading && logFiles.length) {
@if (loading && 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>
@for (log of logs; track $index) {
<p class="m-0 p-0 log-entry-{{getLogLevel(log)}}">{{log}}</p>
}
</div>

View File

@@ -1,18 +1,13 @@
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { BrowserModule, By } from '@angular/platform-browser'
import { NgbModule, NgbNavLink } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { LogService } from 'src/app/services/rest/log.service'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { LogsComponent } from './logs.component'
import { of, throwError } from 'rxjs'
import { provideHttpClientTesting } 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'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
const paperless_logs = [
'[2023-05-29 03:05:01,224] [DEBUG] [paperless.tasks] Training data unchanged.',
@@ -37,11 +32,12 @@ describe('LogsComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [LogsComponent, PageHeaderComponent],
imports: [
BrowserModule,
NgbModule,
NgxBootstrapIconsModule.pick(allIcons),
LogsComponent,
PageHeaderComponent,
],
providers: [
provideHttpClient(withInterceptorsFromDi()),
@@ -90,8 +86,7 @@ describe('LogsComponent', () => {
jest.advanceTimersByTime(6000)
expect(reloadSpy).toHaveBeenCalledTimes(2)
component.toggleAutoRefresh()
expect(component.autoRefreshInterval).toBeNull()
component.autoRefreshEnabled = false
jest.advanceTimersByTime(6000)
expect(reloadSpy).toHaveBeenCalledTimes(2)
})

View File

@@ -1,24 +1,39 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
OnDestroy,
OnInit,
ViewChild,
OnDestroy,
ChangeDetectorRef,
} from '@angular/core'
import { Subject, takeUntil } from 'rxjs'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'
import { filter, takeUntil, timer } from 'rxjs'
import { LogService } from 'src/app/services/rest/log.service'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({
selector: 'pngx-logs',
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'],
imports: [
PageHeaderComponent,
NgbNavModule,
FormsModule,
ReactiveFormsModule,
],
})
export class LogsComponent implements OnInit, OnDestroy {
export class LogsComponent
extends LoadingComponentWithPermissions
implements OnInit, OnDestroy
{
constructor(
private logService: LogService,
private changedetectorRef: ChangeDetectorRef
) {}
) {
super()
}
public logs: string[] = []
@@ -26,50 +41,50 @@ export class LogsComponent implements OnInit, OnDestroy {
public activeLog: string
private unsubscribeNotifier: Subject<any> = new Subject()
public isLoading: boolean = false
public autoRefreshInterval: any
public autoRefreshEnabled: boolean = true
@ViewChild('logContainer') logContainer: ElementRef
ngOnInit(): void {
this.isLoading = true
this.logService
.list()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((result) => {
this.logFiles = result
this.isLoading = false
this.loading = false
if (this.logFiles.length > 0) {
this.activeLog = this.logFiles[0]
this.reloadLogs()
}
this.toggleAutoRefresh()
timer(5000, 5000)
.pipe(
filter(() => this.autoRefreshEnabled),
takeUntil(this.unsubscribeNotifier)
)
.subscribe(() => {
this.reloadLogs()
})
})
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(true)
this.unsubscribeNotifier.complete()
clearInterval(this.autoRefreshInterval)
super.ngOnDestroy()
}
reloadLogs() {
this.isLoading = true
this.loading = true
this.logService
.get(this.activeLog)
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe({
next: (result) => {
this.logs = result
this.isLoading = false
this.loading = false
this.scrollToBottom()
},
error: () => {
this.logs = []
this.isLoading = false
this.loading = false
},
})
}
@@ -96,15 +111,4 @@ export class LogsComponent implements OnInit, OnDestroy {
behavior: 'auto',
})
}
toggleAutoRefresh(): void {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval)
this.autoRefreshInterval = null
} else {
this.autoRefreshInterval = setInterval(() => {
this.reloadLogs()
}, 5000)
}
}
}

View File

@@ -1,7 +1,7 @@
<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>."
info="Options to customize appearance, notifications and more. Settings apply to the <strong>current user only</strong>."
i18n-info
>
<button class="btn btn-sm btn-outline-primary" (click)="tourService.start()">
@@ -39,193 +39,204 @@
<li [ngbNavItem]="SettingsNavIDs.General">
<a ngbNavLink i18n>General</a>
<ng-template ngbNavContent>
<h4 i18n>Appearance</h4>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Display language</span>
</div>
<div class="col">
<select class="form-select" formControlName="displayLanguage">
@for (lang of displayLanguageOptions; track lang) {
<option [ngValue]="lang.code">{{lang.name}}@if (lang.code && currentLocale !== 'en-US') {
<span> - {{lang.englishName}}</span>
}</option>
}
</select>
@if (displayLanguageIsDirty) {
<small class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
}
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Date display</span>
</div>
<div class="col">
<select class="form-select" formControlName="dateLocale">
@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>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Date format</span>
</div>
<div class="col">
<div class="form-check">
<input type="radio" id="dateFormatShort" name="dateFormat" class="form-check-input" formControlName="dateFormat" value="shortDate">
<label class="form-check-label" for="dateFormatShort" i18n>Short: {{today | customDate:'shortDate':null:computedDateLocale}}</label>
</div>
<div class="form-check">
<input type="radio" id="dateFormatMedium" name="dateFormat" class="form-check-input" formControlName="dateFormat" value="mediumDate">
<label class="form-check-label" for="dateFormatMedium" i18n>Medium: {{today | customDate:'mediumDate':null:computedDateLocale}}</label>
</div>
<div class="form-check">
<input type="radio" id="dateFormatLong" name="dateFormat" class="form-check-input" formControlName="dateFormat" value="longDate">
<label class="form-check-label" for="dateFormatLong" i18n>Long: {{today | customDate:'longDate':null:computedDateLocale}}</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Items per page</span>
</div>
<div class="col">
<select class="form-select" formControlName="documentListItemPerPage">
<option [ngValue]="10">10</option>
<option [ngValue]="25">25</option>
<option [ngValue]="50">50</option>
<option [ngValue]="100">100</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Document editor</span>
</div>
<div class="col">
<pngx-input-check i18n-title title="Use PDF viewer provided by the browser" i18n-hint hint="This is usually faster for displaying large PDF documents, but it might not work on some browsers." formControlName="useNativePdfViewer"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Sidebar</span>
</div>
<div class="col">
<pngx-input-check i18n-title title="Use 'slim' sidebar (icons only)" formControlName="slimSidebarEnabled"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Dark mode</span>
</div>
<div class="col">
<pngx-input-check i18n-title title="Use system settings" formControlName="darkModeUseSystem"></pngx-input-check>
<pngx-input-check [hidden]="settingsForm.value.darkModeUseSystem" i18n-title title="Enable dark mode" formControlName="darkModeEnabled"></pngx-input-check>
<pngx-input-check i18n-title title="Invert thumbnails in dark mode" formControlName="darkModeInvertThumbs"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Theme Color</span>
</div>
<div class="col col-md-3">
<pngx-input-color i18n-title formControlName="themeColor" [error]="error?.color"></pngx-input-color>
</div>
<div class="col-2">
<button class="btn btn-link btn-sm pt-2 ps-0" [disabled]="!this.settingsForm.get('themeColor').value" (click)="clearThemeColor()">
<i-bs width="1em" height="1em" name="x"></i-bs><ng-container i18n>Reset</ng-container>
</button>
</div>
</div>
<h4 class="mt-4" id="update-checking" i18n>Update checking</h4>
<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>
<p i18n>
<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">
<div class="offset-md-3 col">
<pngx-input-check i18n-title title="Show confirmation dialogs" formControlName="bulkEditConfirmationDialogs"></pngx-input-check>
<pngx-input-check i18n-title title="Apply on close" formControlName="bulkEditApplyOnClose"></pngx-input-check>
</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="Do not include advanced search results" formControlName="searchDbOnly"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="offset-md-3 col">
<div class="row">
<div class="col-md-2 col-form-label pt-0">
<span i18n>Full search links to</span>
<div class="row">
<div class="col-xl-6 pe-xl-5">
<h4 i18n>Appearance</h4>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Display language</span>
</div>
<div class="col">
<select class="form-select" formControlName="searchLink">
<option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option>
<option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option>
<select class="form-select" formControlName="displayLanguage">
@for (lang of displayLanguageOptions; track lang) {
<option [ngValue]="lang.code">{{lang.name}}@if (lang.code && currentLocale !== 'en-US') {
<span> - {{lang.englishName}}</span>
}</option>
}
</select>
@if (displayLanguageIsDirty) {
<small class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
}
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Date display</span>
</div>
<div class="col">
<select class="form-select" formControlName="dateLocale">
@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>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Date format</span>
</div>
<div class="col">
<div class="form-check">
<input type="radio" id="dateFormatShort" name="dateFormat" class="form-check-input" formControlName="dateFormat" value="shortDate">
<label class="form-check-label" for="dateFormatShort" i18n>Short: {{today | customDate:'shortDate':null:computedDateLocale}}</label>
</div>
<div class="form-check">
<input type="radio" id="dateFormatMedium" name="dateFormat" class="form-check-input" formControlName="dateFormat" value="mediumDate">
<label class="form-check-label" for="dateFormatMedium" i18n>Medium: {{today | customDate:'mediumDate':null:computedDateLocale}}</label>
</div>
<div class="form-check">
<input type="radio" id="dateFormatLong" name="dateFormat" class="form-check-input" formControlName="dateFormat" value="longDate">
<label class="form-check-label" for="dateFormatLong" i18n>Long: {{today | customDate:'longDate':null:computedDateLocale}}</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Items per page</span>
</div>
<div class="col">
<select class="form-select" formControlName="documentListItemPerPage">
<option [ngValue]="10">10</option>
<option [ngValue]="25">25</option>
<option [ngValue]="50">50</option>
<option [ngValue]="100">100</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Sidebar</span>
</div>
<div class="col">
<pngx-input-check i18n-title title="Use 'slim' sidebar (icons only)" formControlName="slimSidebarEnabled"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Dark mode</span>
</div>
<div class="col">
<pngx-input-check i18n-title title="Use system settings" formControlName="darkModeUseSystem"></pngx-input-check>
<pngx-input-check [hidden]="settingsForm.value.darkModeUseSystem" i18n-title title="Enable dark mode" formControlName="darkModeEnabled"></pngx-input-check>
<pngx-input-check i18n-title title="Invert thumbnails in dark mode" formControlName="darkModeInvertThumbs"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Theme Color</span>
</div>
<div class="col col-md-4">
<pngx-input-color i18n-title formControlName="themeColor" [error]="error?.color"></pngx-input-color>
</div>
<div class="col-2">
<button class="btn btn-link btn-sm pt-2 ps-0" [disabled]="!this.settingsForm.get('themeColor').value" (click)="clearThemeColor()">
<i-bs width="1em" height="1em" name="x"></i-bs><ng-container i18n>Reset</ng-container>
</button>
</div>
</div>
<h4 class="mt-4" i18n>Document editing</h4>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Use PDF viewer provided by the browser" i18n-hint hint="This is usually faster for displaying large PDF documents, but it might not work on some browsers." formControlName="useNativePdfViewer"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Automatically remove inbox tag(s) on save" formControlName="documentEditingRemoveInboxTags"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Show document thumbnail during loading" formControlName="documentEditingOverlayThumbnail"></pngx-input-check>
</div>
</div>
</div>
</div>
<div class="col-xl-6 ps-xl-5">
<h4 class="mt-4 mt-md-0" id="update-checking" i18n>Update checking</h4>
<div class="row mb-3">
<div class="col d-flex flex-row align-items-start">
<pngx-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled"></pngx-input-check>
<button class="btn btn-sm btn-link text-muted me-auto p-0 ms-2" title="What's this?" i18n-title type="button" triggers="mouseenter:mouseleave" [ngbPopover]="updatesPopover" [autoClose]="true">
<i-bs name="question-circle"></i-bs>
</button>
<ng-template #updatesPopover>
<p i18n>
Update checking works by pinging the public GitHub API for the latest release to determine whether a new version is available. Actual updating of the app must still be performed manually.
</p>
<p>
<em i18n>No tracking data is collected by the app in any way.</em>
</p>
</ng-template>
</div>
</div>
<h4 class="mt-4" i18n>Notes</h4>
<h4 class="mt-4" i18n>Bulk editing</h4>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Show confirmation dialogs" formControlName="bulkEditConfirmationDialogs"></pngx-input-check>
<pngx-input-check i18n-title title="Apply on close" formControlName="bulkEditApplyOnClose"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="offset-md-3 col">
<pngx-input-check i18n-title title="Enable notes" formControlName="notesEnabled"></pngx-input-check>
<h4 class="mt-4" i18n>Global search</h4>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Do not include advanced search results" formControlName="searchDbOnly"></pngx-input-check>
</div>
</div>
<div class="row mb-3">
<div class="col">
<div class="row">
<div class="col-md-3 col-form-label pt-0">
<span i18n>Full search links to</span>
</div>
<div class="col">
<select class="form-select" formControlName="searchLink">
<option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option>
<option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option>
</select>
</div>
</div>
</div>
</div>
<h4 class="mt-4" i18n>Saved Views</h4>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check>
</div>
</div>
<h4 class="mt-4" i18n>Notes</h4>
<div class="row mb-3">
<div class="col">
<pngx-input-check i18n-title title="Enable notes" formControlName="notesEnabled"></pngx-input-check>
</div>
</div>
</div>
</div>
@@ -239,7 +250,7 @@
<h4 i18n>Default Permissions</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<div class="col">
<p i18n>
Settings apply to this user account for objects (Tags, Mail Rules, etc.) created via the web UI
</p>
@@ -321,7 +332,7 @@
<h4 i18n>Document processing</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<div class="col">
<pngx-input-check i18n-title title="Show notifications when new documents are detected" formControlName="notificationsConsumerNewDocument"></pngx-input-check>
<pngx-input-check i18n-title title="Show notifications when document processing completes successfully" formControlName="notificationsConsumerSuccess"></pngx-input-check>
<pngx-input-check i18n-title title="Show notifications when document processing fails" formControlName="notificationsConsumerFailed"></pngx-input-check>
@@ -331,87 +342,6 @@
</ng-template>
</li>
<li [ngbNavItem]="SettingsNavIDs.SavedViews" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }">
<a ngbNavLink i18n>Saved views</a>
<ng-template ngbNavContent>
<h4 i18n>Settings</h4>
<div class="row mb-3">
<div class="offset-md-3 col">
<pngx-input-check i18n-title title="Show warning when closing saved views with unsaved changes" formControlName="savedViewsWarnOnUnsavedChange"></pngx-input-check>
</div>
</div>
<h4 i18n>Views</h4>
<ul class="list-group" formGroupName="savedViews">
@for (view of savedViews; track view) {
<li class="list-group-item py-3">
<div [formGroupName]="view.id">
<div class="row">
<div class="col">
<pngx-input-text title="Name" formControlName="name"></pngx-input-text>
</div>
<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="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>
}
@if (savedViews && savedViews.length === 0) {
<li class="list-group-item">
<div i18n>No saved views defined.</div>
</li>
}
@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>
</li>
}
</ul>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>

View File

@@ -1,35 +1,45 @@
import { ViewportScroller, DatePipe } from '@angular/common'
import { DragDropModule } from '@angular/cdk/drag-drop'
import { DatePipe, ViewportScroller } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import { Router, ActivatedRoute, convertToParamMap } from '@angular/router'
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import {
NgbModule,
NgbAlertModule,
NgbNavLink,
NgbModal,
NgbModalModule,
NgbModule,
NgbNavLink,
} from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import { SavedView } from 'src/app/data/saved-view'
import {
InstallType,
SystemStatus,
SystemStatusItemStatus,
} from 'src/app/data/system-status'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
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'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { PermissionsService } from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService, Toast } from 'src/app/services/toast.service'
import { SystemStatusService } from 'src/app/services/system-status.service'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { ConfirmButtonComponent } from '../../common/confirm-button/confirm-button.component'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { CheckComponent } from '../../common/input/check/check.component'
import { ColorComponent } from '../../common/input/color/color.component'
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
@@ -37,25 +47,9 @@ import { SelectComponent } from '../../common/input/select/select.component'
import { TagsComponent } from '../../common/input/tags/tags.component'
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'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { SettingsComponent } from './settings.component'
const savedViews = [
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
{ id: 2, name: 'view2', show_in_sidebar: false, show_on_dashboard: false },
]
const users = [
{ id: 1, username: 'user1', is_superuser: false },
{ id: 2, username: 'user2', is_superuser: false },
@@ -70,7 +64,6 @@ describe('SettingsComponent', () => {
let fixture: ComponentFixture<SettingsComponent>
let router: Router
let settingsService: SettingsService
let savedViewService: SavedViewService
let activatedRoute: ActivatedRoute
let viewportScroller: ViewportScroller
let toastService: ToastService
@@ -82,7 +75,16 @@ describe('SettingsComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
imports: [
NgbModule,
RouterTestingModule.withRoutes(routes),
FormsModule,
ReactiveFormsModule,
NgbAlertModule,
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
NgbModalModule,
DragDropModule,
SettingsComponent,
PageHeaderComponent,
IfPermissionsDirective,
@@ -101,17 +103,6 @@ describe('SettingsComponent', () => {
ConfirmButtonComponent,
DragDropSelectComponent,
],
imports: [
NgbModule,
RouterTestingModule.withRoutes(routes),
FormsModule,
ReactiveFormsModule,
NgbAlertModule,
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
NgbModalModule,
DragDropModule,
],
providers: [
CustomDatePipe,
DatePipe,
@@ -139,7 +130,6 @@ describe('SettingsComponent', () => {
.spyOn(permissionsService, 'currentUserOwnsObject')
.mockReturnValue(true)
groupService = TestBed.inject(GroupService)
savedViewService = TestBed.inject(SavedViewService)
})
function completeSetup(excludeService = null) {
@@ -161,15 +151,6 @@ describe('SettingsComponent', () => {
})
)
}
if (excludeService !== savedViewService) {
jest.spyOn(savedViewService, 'listAll').mockReturnValue(
of({
all: savedViews.map((v) => v.id),
count: savedViews.length,
results: (savedViews as SavedView[]).concat([]),
})
)
}
fixture = TestBed.createComponent(SettingsComponent)
component = fixture.componentInstance
@@ -184,8 +165,6 @@ describe('SettingsComponent', () => {
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'permissions'])
tabButtons[2].nativeElement.dispatchEvent(new MouseEvent('click'))
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'notifications'])
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click'))
expect(navigateSpy).toHaveBeenCalledWith(['settings', 'savedviews'])
const initSpy = jest.spyOn(component, 'initialize')
component.isDirty = true // mock dirty
@@ -213,90 +192,8 @@ describe('SettingsComponent', () => {
expect(scrollSpy).toHaveBeenCalledWith('#notifications')
})
it('should enable organizing of sidebar saved views even on direct navigation', () => {
completeSetup()
jest
.spyOn(activatedRoute, 'paramMap', 'get')
.mockReturnValue(of(convertToParamMap({ section: 'savedviews' })))
activatedRoute.snapshot.fragment = '#savedviews'
component.ngOnInit()
expect(component.activeNavID).toEqual(4) // Saved Views
component.ngAfterViewInit()
expect(settingsService.organizingSidebarSavedViews).toBeTruthy()
})
it('should support save saved views, show error', () => {
completeSetup()
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click'))
fixture.detectChanges()
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastSpy = jest.spyOn(toastService, 'show')
const savedViewPatchSpy = jest.spyOn(savedViewService, 'patchMany')
const toggle = fixture.debugElement.query(
By.css('.form-check.form-switch input')
)
toggle.nativeElement.checked = true
toggle.nativeElement.dispatchEvent(new Event('change'))
// saved views error first
savedViewPatchSpy.mockReturnValueOnce(
throwError(() => new Error('unable to save saved views'))
)
component.saveSettings()
expect(toastErrorSpy).toHaveBeenCalled()
expect(savedViewPatchSpy).toHaveBeenCalled()
toastSpy.mockClear()
toastErrorSpy.mockClear()
savedViewPatchSpy.mockClear()
// succeed saved views
savedViewPatchSpy.mockReturnValueOnce(of(savedViews as SavedView[]))
component.saveSettings()
expect(toastErrorSpy).not.toHaveBeenCalled()
expect(savedViewPatchSpy).toHaveBeenCalled()
})
it('should update only patch saved views that have changed', () => {
completeSetup()
const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink))
tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click'))
fixture.detectChanges()
const patchSpy = jest.spyOn(savedViewService, 'patchMany')
component.saveSettings()
expect(patchSpy).not.toHaveBeenCalled()
const view = savedViews[0]
const toggle = fixture.debugElement.query(
By.css('.form-check.form-switch input')
)
toggle.nativeElement.checked = true
toggle.nativeElement.dispatchEvent(new Event('change'))
// register change
component.savedViewGroup.get(view.id.toString()).value[
'show_on_dashboard'
] = !view.show_on_dashboard
fixture.detectChanges()
component.saveSettings()
expect(patchSpy).toHaveBeenCalledWith([
{
id: view.id,
name: view.name,
show_in_sidebar: view.show_in_sidebar,
show_on_dashboard: !view.show_on_dashboard,
},
])
})
it('should support save local settings updating appearance settings and calling API, show error', () => {
completeSetup()
jest.spyOn(savedViewService, 'patchMany').mockReturnValue(of([]))
const toastErrorSpy = jest.spyOn(toastService, 'showError')
const toastSpy = jest.spyOn(toastService, 'show')
const storeSpy = jest.spyOn(settingsService, 'storeSettings')
@@ -315,7 +212,7 @@ describe('SettingsComponent', () => {
expect(toastErrorSpy).toHaveBeenCalled()
expect(storeSpy).toHaveBeenCalled()
expect(appearanceSettingsSpy).not.toHaveBeenCalled()
expect(setSpy).toHaveBeenCalledTimes(27)
expect(setSpy).toHaveBeenCalledTimes(28)
// succeed
storeSpy.mockReturnValueOnce(of(true))
@@ -326,7 +223,6 @@ describe('SettingsComponent', () => {
it('should offer reload if settings changes require', () => {
completeSetup()
jest.spyOn(savedViewService, 'patchMany').mockReturnValue(of([]))
let toast: Toast
toastService.getToasts().subscribe((t) => (toast = t[0]))
component.initialize(true) // reset
@@ -361,18 +257,6 @@ describe('SettingsComponent', () => {
component.clearThemeColor()
})
it('should support delete saved view', () => {
completeSetup()
const toastSpy = jest.spyOn(toastService, 'showInfo')
const deleteSpy = jest.spyOn(savedViewService, 'delete')
deleteSpy.mockReturnValue(of(true))
component.deleteSavedView(savedViews[0] as SavedView)
expect(deleteSpy).toHaveBeenCalled()
expect(toastSpy).toHaveBeenCalledWith(
`Saved view "${savedViews[0].name}" deleted.`
)
})
it('should show errors on load if load users failure', () => {
const toastErrorSpy = jest.spyOn(toastService, 'showError')
jest

View File

@@ -1,56 +1,69 @@
import { ViewportScroller } from '@angular/common'
import { AsyncPipe, ViewportScroller } from '@angular/common'
import {
Component,
OnInit,
AfterViewInit,
OnDestroy,
Component,
Inject,
LOCALE_ID,
OnDestroy,
OnInit,
} from '@angular/core'
import { FormGroup, FormControl } from '@angular/forms'
import {
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
} from '@angular/forms'
import { ActivatedRoute, Router } from '@angular/router'
import {
NgbModal,
NgbModalRef,
NgbNavChangeEvent,
NgbNavModule,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { DirtyComponent, dirtyCheck } from '@ngneat/dirty-check-forms'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import {
BehaviorSubject,
Subscription,
Observable,
Subject,
Subscription,
first,
takeUntil,
tap,
} from 'rxjs'
import { Group } from 'src/app/data/group'
import { SavedView } from 'src/app/data/saved-view'
import {
SystemStatus,
SystemStatusItemStatus,
} from 'src/app/data/system-status'
import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import {
PermissionsService,
PermissionAction,
PermissionType,
PermissionsService,
} from 'src/app/services/permissions.service'
import { GroupService } from 'src/app/services/rest/group.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { UserService } from 'src/app/services/rest/user.service'
import {
SettingsService,
LanguageOption,
SettingsService,
} 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'
import { Toast, ToastService } from 'src/app/services/toast.service'
import { CheckComponent } from '../../common/input/check/check.component'
import { ColorComponent } from '../../common/input/color/color.component'
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { SystemStatusDialogComponent } from '../../common/system-status-dialog/system-status-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
enum SettingsNavIDs {
General = 1,
@@ -69,15 +82,28 @@ const systemDateFormat = {
selector: 'pngx-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
imports: [
PageHeaderComponent,
CheckComponent,
ColorComponent,
SelectComponent,
PermissionsGroupComponent,
PermissionsUserComponent,
CustomDatePipe,
IfPermissionsDirective,
AsyncPipe,
FormsModule,
ReactiveFormsModule,
NgbNavModule,
NgbPopoverModule,
NgxBootstrapIconsModule,
],
})
export class SettingsComponent
extends ComponentWithPermissions
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
{
activeNavID: number
DisplayMode = DisplayMode
savedViewGroup = new FormGroup({})
settingsForm = new FormGroup({
bulkEditConfirmationDialogs: new FormControl(null),
@@ -88,7 +114,6 @@ export class SettingsComponent
darkModeEnabled: new FormControl(null),
darkModeInvertThumbs: new FormControl(null),
themeColor: new FormControl(null),
useNativePdfViewer: new FormControl(null),
displayLanguage: new FormControl(null),
dateLocale: new FormControl(null),
dateFormat: new FormControl(null),
@@ -99,7 +124,9 @@ export class SettingsComponent
defaultPermsViewGroups: new FormControl(null),
defaultPermsEditUsers: new FormControl(null),
defaultPermsEditGroups: new FormControl(null),
useNativePdfViewer: new FormControl(null),
documentEditingRemoveInboxTags: new FormControl(null),
documentEditingOverlayThumbnail: new FormControl(null),
searchDbOnly: new FormControl(null),
searchLink: new FormControl(null),
@@ -109,14 +136,9 @@ export class SettingsComponent
notificationsConsumerSuppressOnDashboard: new FormControl(null),
savedViewsWarnOnUnsavedChange: new FormControl(null),
savedViews: this.savedViewGroup,
})
savedViews: SavedView[]
SettingsNavIDs = SettingsNavIDs
get displayFields() {
return this.settings.allDisplayFields
}
store: BehaviorSubject<any>
storeSub: Subscription
@@ -151,7 +173,6 @@ export class SettingsComponent
}
constructor(
public savedViewService: SavedViewService,
private documentListViewService: DocumentListViewService,
private toastService: ToastService,
private settings: SettingsService,
@@ -213,18 +234,6 @@ export class SettingsComponent
})
}
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.SavedView
)
) {
this.savedViewService.listAll().subscribe((r) => {
this.savedViews = r.results
this.initialize(false)
})
}
this.activatedRoute.paramMap.subscribe((paramMap) => {
const section = paramMap.get('section')
if (section) {
@@ -234,9 +243,6 @@ export class SettingsComponent
if (navIDKey) {
this.activeNavID = SettingsNavIDs[navIDKey]
}
if (this.activeNavID === SettingsNavIDs.SavedViews) {
this.settings.organizingSidebarSavedViews = true
}
}
})
}
@@ -308,9 +314,11 @@ export class SettingsComponent
documentEditingRemoveInboxTags: this.settings.get(
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS
),
documentEditingOverlayThumbnail: this.settings.get(
SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL
),
searchDbOnly: this.settings.get(SETTINGS_KEYS.SEARCH_DB_ONLY),
searchLink: this.settings.get(SETTINGS_KEYS.SEARCH_FULL_TYPE),
savedViews: {},
}
}
@@ -323,15 +331,11 @@ export class SettingsComponent
this.router
.navigate(['settings', foundNavIDkey.toLowerCase()])
.then((navigated) => {
this.settings.organizingSidebarSavedViews = false
if (!navigated && this.isDirty) {
this.activeNavID = navChangeEvent.activeId
} else if (navigated && this.isDirty) {
this.initialize()
}
if (this.activeNavID === SettingsNavIDs.SavedViews) {
this.settings.organizingSidebarSavedViews = true
}
})
}
@@ -342,34 +346,6 @@ export class SettingsComponent
let storeData = this.getCurrentSettings()
if (this.savedViews) {
this.emptyGroup(this.savedViewGroup)
for (let view of this.savedViews) {
storeData.savedViews[view.id.toString()] = {
id: view.id,
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(),
new FormGroup({
id: new FormControl(null),
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([]),
})
)
}
}
this.store = new BehaviorSubject(storeData)
this.storeSub = this.store.asObservable().subscribe((state) => {
@@ -409,32 +385,12 @@ export class SettingsComponent
}
}
private emptyGroup(group: FormGroup) {
Object.keys(group.controls).forEach((key) => group.removeControl(key))
}
ngOnDestroy() {
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: SavedView) {
this.savedViewService.delete(savedView).subscribe(() => {
this.savedViewGroup.removeControl(savedView.id.toString())
this.savedViews.splice(this.savedViews.indexOf(savedView), 1)
this.toastService.showInfo(
$localize`Saved view "${savedView.name}" deleted.`
)
this.savedViewService.clearCache()
this.savedViewService.listAll().subscribe((r) => {
this.savedViews = r.results
this.initialize(true)
})
})
}
private saveLocalSettings() {
public saveSettings() {
this.savePending = true
const reloadRequired =
this.settingsForm.value.displayLanguage !=
@@ -539,6 +495,10 @@ export class SettingsComponent
SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS,
this.settingsForm.value.documentEditingRemoveInboxTags
)
this.settings.set(
SETTINGS_KEYS.DOCUMENT_EDITING_OVERLAY_THUMBNAIL,
this.settingsForm.value.documentEditingOverlayThumbnail
)
this.settings.set(
SETTINGS_KEYS.SEARCH_DB_ONLY,
this.settingsForm.value.searchDbOnly
@@ -592,31 +552,6 @@ export class SettingsComponent
return new Date()
}
saveSettings() {
// only patch views that have actually changed
const changed: SavedView[] = []
Object.values(this.savedViewGroup.controls)
.filter((g: FormGroup) => !g.pristine)
.forEach((group: FormGroup) => {
changed.push(group.value)
})
if (changed.length > 0) {
this.savedViewService.patchMany(changed).subscribe({
next: () => {
this.saveLocalSettings()
},
error: (error) => {
this.toastService.showError(
$localize`Error while storing settings on server.`,
error
)
},
})
} else {
this.saveLocalSettings()
}
}
reset() {
this.settingsForm.patchValue(this.store.getValue())
}

View File

@@ -4,15 +4,40 @@
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">
<div class="btn-toolbar col col-md-auto align-items-center gap-2">
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedTasks.size === 0">
<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">
<button class="btn btn-sm btn-outline-primary me-2" (click)="dismissTasks()" *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
<i-bs name="check2-all"></i-bs>&nbsp;{{dismissButtonText}}
</button>
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" role="switch" id="autoRefreshSwitch" (click)="toggleAutoRefresh()" [attr.checked]="autoRefreshInterval">
<div class="form-inline d-flex align-items-center">
<div class="input-group input-group-sm flex-fill w-auto flex-nowrap">
<span class="input-group-text text-muted" i18n>Filter by</span>
@if (filterTargets.length > 1) {
<div ngbDropdown>
<button class="btn btn-sm btn-outline-primary" ngbDropdownToggle>{{filterTargetName}}</button>
<div class="dropdown-menu shadow" ngbDropdownMenu>
@for (t of filterTargets; track t.id) {
<button ngbDropdownItem [class.active]="filterTargetID === t.id" (click)="filterTargetID = t.id">{{t.name}}</button>
}
</div>
</div>
} @else {
<span class="input-group-text">{{filterTargetName}}</span>
}
@if (filterText?.length) {
<button class="btn btn-link btn-sm px-2 position-absolute top-0 end-0 z-10" (click)="resetFilter()">
<i-bs width="1em" height="1em" name="x"></i-bs>
</button>
}
<input #filterInput class="form-control form-control-sm" type="text"
(keyup)="filterInputKeyup($event)"
[(ngModel)]="filterText">
</div>
</div>
<div class="form-check form-switch mb-0 ms-2">
<input class="form-check-input" type="checkbox" role="switch" [(ngModel)]="autoRefreshEnabled">
<label class="form-check-label" for="autoRefreshSwitch" i18n>Auto refresh</label>
</div>
</div>
@@ -43,7 +68,7 @@
</tr>
</thead>
<tbody>
@for (task of tasks | slice: (page-1) * pageSize : page * pageSize; track task) {
@for (task of tasks | slice: (page-1) * pageSize : page * pageSize; track task.id) {
<tr (click)="toggleSelected(task, $event); $event.stopPropagation();">
<td>
<div class="form-check">
@@ -118,13 +143,13 @@
</div>
</ng-template>
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" (hidden)="duringTabChange($event)">
<ul ngbNav #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" (hidden)="duringTabChange()" (navChange)="beforeTabChange()">
<li ngbNavItem="failed">
<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-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:currentTasks}"></ng-container>
</ng-template>
</li>
<li ngbNavItem="completed">
@@ -132,7 +157,7 @@
<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-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:currentTasks}"></ng-container>
</ng-template>
</li>
<li ngbNavItem="started">
@@ -140,7 +165,7 @@
<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-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:currentTasks}"></ng-container>
</ng-template>
</li>
<li ngbNavItem="queued">
@@ -148,7 +173,7 @@
<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-container [ngTemplateOutlet]="tasksTemplate" [ngTemplateOutletContext]="{tasks:currentTasks}"></ng-container>
</ng-template>
</li>
</ul>

View File

@@ -26,3 +26,14 @@ pre {
max-width: 150px;
}
}
.input-group .dropdown .btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.z-10 {
z-index: 10;
}

View File

@@ -1,36 +1,36 @@
import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import {
HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import { Router } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import {
NgbModal,
NgbModalRef,
NgbModule,
NgbNavItem,
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { routes } from 'src/app/app-routing.module'
import {
PaperlessTask,
PaperlessTaskType,
PaperlessTaskStatus,
PaperlessTaskType,
} from 'src/app/data/paperless-task'
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'
import { PermissionsService } from 'src/app/services/permissions.service'
import { TasksService } from 'src/app/services/tasks.service'
import { environment } from 'src/environments/environment'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
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'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { TasksComponent, TaskTab } from './tasks.component'
const tasks: PaperlessTask[] = [
{
@@ -119,18 +119,16 @@ describe('TasksComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
TasksComponent,
PageHeaderComponent,
IfPermissionsDirective,
CustomDatePipe,
ConfirmDialogComponent,
],
imports: [
NgbModule,
RouterTestingModule.withRoutes(routes),
NgxBootstrapIconsModule.pick(allIcons),
FormsModule,
TasksComponent,
PageHeaderComponent,
IfPermissionsDirective,
CustomDatePipe,
ConfirmDialogComponent,
],
providers: [
{
@@ -167,7 +165,7 @@ describe('TasksComponent', () => {
let currentTasksLength = tasks.filter(
(t) => t.status === PaperlessTaskStatus.Failed
).length
component.activeTab = 'failed'
component.activeTab = TaskTab.Failed
fixture.detectChanges()
expect(tabButtons[0].nativeElement.textContent).toEqual(
`Failed${currentTasksLength}`
@@ -179,7 +177,7 @@ describe('TasksComponent', () => {
currentTasksLength = tasks.filter(
(t) => t.status === PaperlessTaskStatus.Complete
).length
component.activeTab = 'completed'
component.activeTab = TaskTab.Completed
fixture.detectChanges()
expect(tabButtons[1].nativeElement.textContent).toEqual(
`Complete${currentTasksLength}`
@@ -188,7 +186,7 @@ describe('TasksComponent', () => {
currentTasksLength = tasks.filter(
(t) => t.status === PaperlessTaskStatus.Started
).length
component.activeTab = 'started'
component.activeTab = TaskTab.Started
fixture.detectChanges()
expect(tabButtons[2].nativeElement.textContent).toEqual(
`Started${currentTasksLength}`
@@ -197,7 +195,7 @@ describe('TasksComponent', () => {
currentTasksLength = tasks.filter(
(t) => t.status === PaperlessTaskStatus.Pending
).length
component.activeTab = 'queued'
component.activeTab = TaskTab.Queued
fixture.detectChanges()
expect(tabButtons[3].nativeElement.textContent).toEqual(
`Queued${currentTasksLength}`
@@ -206,7 +204,7 @@ describe('TasksComponent', () => {
it('should to go page 1 between tab switch', () => {
component.page = 10
component.duringTabChange(2)
component.duringTabChange()
expect(component.page).toEqual(1)
})
@@ -283,10 +281,63 @@ describe('TasksComponent', () => {
expect(reloadSpy).toHaveBeenCalledTimes(1)
jest.advanceTimersByTime(5000)
expect(reloadSpy).toHaveBeenCalledTimes(2)
component.toggleAutoRefresh()
expect(component.autoRefreshInterval).toBeNull()
component.autoRefreshEnabled = false
jest.advanceTimersByTime(6000)
expect(reloadSpy).toHaveBeenCalledTimes(2)
})
it('should filter tasks by file name', () => {
const input = fixture.debugElement.query(
By.css('pngx-page-header input[type=text]')
)
input.nativeElement.value = '191092'
input.nativeElement.dispatchEvent(new Event('input'))
jest.advanceTimersByTime(150) // debounce time
fixture.detectChanges()
expect(component.filterText).toEqual('191092')
expect(
fixture.debugElement.queryAll(By.css('table tbody tr')).length
).toEqual(2) // 1 task x 2 lines
})
it('should filter tasks by result', () => {
component.activeTab = TaskTab.Failed
fixture.detectChanges()
component.filterTargetID = 1
const input = fixture.debugElement.query(
By.css('pngx-page-header input[type=text]')
)
input.nativeElement.value = 'duplicate'
input.nativeElement.dispatchEvent(new Event('input'))
jest.advanceTimersByTime(150) // debounce time
fixture.detectChanges()
expect(component.filterText).toEqual('duplicate')
expect(
fixture.debugElement.queryAll(By.css('table tbody tr')).length
).toEqual(4) // 2 tasks x 2 lines
})
it('should support keyboard events for filtering', () => {
const input = fixture.debugElement.query(
By.css('pngx-page-header input[type=text]')
)
input.nativeElement.value = '191092'
input.nativeElement.dispatchEvent(
new KeyboardEvent('keyup', { key: 'Enter' })
)
expect(component.filterText).toEqual('191092') // no debounce needed
input.nativeElement.dispatchEvent(
new KeyboardEvent('keyup', { key: 'Escape' })
)
expect(component.filterText).toEqual('')
})
it('should reset filter and target on tab switch', () => {
component.filterText = '191092'
component.filterTargetID = 1
component.activeTab = TaskTab.Completed
component.beforeTabChange()
expect(component.filterText).toEqual('')
expect(component.filterTargetID).toEqual(0)
})
})

View File

@@ -1,22 +1,75 @@
import { Component, OnInit, OnDestroy } from '@angular/core'
import { NgTemplateOutlet, SlicePipe } from '@angular/common'
import { Component, OnDestroy, OnInit } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { first } from 'rxjs'
import {
NgbCollapseModule,
NgbDropdownModule,
NgbModal,
NgbNavModule,
NgbPaginationModule,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import {
debounceTime,
distinctUntilChanged,
filter,
first,
Subject,
takeUntil,
timer,
} from 'rxjs'
import { PaperlessTask } from 'src/app/data/paperless-task'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { TasksService } from 'src/app/services/tasks.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
export enum TaskTab {
Queued = 'queued',
Started = 'started',
Completed = 'completed',
Failed = 'failed',
}
enum TaskFilterTargetID {
Name,
Result,
}
const FILTER_TARGETS = [
{ id: TaskFilterTargetID.Name, name: $localize`Name` },
{ id: TaskFilterTargetID.Result, name: $localize`Result` },
]
@Component({
selector: 'pngx-tasks',
templateUrl: './tasks.component.html',
styleUrls: ['./tasks.component.scss'],
imports: [
PageHeaderComponent,
IfPermissionsDirective,
CustomDatePipe,
SlicePipe,
FormsModule,
ReactiveFormsModule,
NgTemplateOutlet,
NgbCollapseModule,
NgbDropdownModule,
NgbNavModule,
NgbPaginationModule,
NgbPopoverModule,
NgxBootstrapIconsModule,
],
})
export class TasksComponent
extends ComponentWithPermissions
extends LoadingComponentWithPermissions
implements OnInit, OnDestroy
{
public activeTab: string
public activeTab: TaskTab
public selectedTasks: Set<number> = new Set()
public togggleAll: boolean = false
public expandedTask: number
@@ -24,7 +77,27 @@ export class TasksComponent
public pageSize: number = 25
public page: number = 1
public autoRefreshInterval: any
public autoRefreshEnabled: boolean = true
private _filterText: string = ''
get filterText() {
return this._filterText
}
set filterText(value: string) {
this.filterDebounce.next(value)
}
public filterTargetID: TaskFilterTargetID = TaskFilterTargetID.Name
public get filterTargetName(): string {
return this.filterTargets.find((t) => t.id == this.filterTargetID).name
}
private filterDebounce: Subject<string> = new Subject<string>()
public get filterTargets(): Array<{ id: number; name: string }> {
return [TaskTab.Failed, TaskTab.Completed].includes(this.activeTab)
? FILTER_TARGETS
: FILTER_TARGETS.slice(0, 1)
}
get dismissButtonText(): string {
return this.selectedTasks.size > 0
@@ -42,12 +115,28 @@ export class TasksComponent
ngOnInit() {
this.tasksService.reload()
this.toggleAutoRefresh()
timer(5000, 5000)
.pipe(
filter(() => this.autoRefreshEnabled),
takeUntil(this.unsubscribeNotifier)
)
.subscribe(() => {
this.tasksService.reload()
})
this.filterDebounce
.pipe(
takeUntil(this.unsubscribeNotifier),
debounceTime(100),
distinctUntilChanged(),
filter((query) => !query.length || query.length > 2)
)
.subscribe((query) => (this._filterText = query))
}
ngOnDestroy() {
super.ngOnDestroy()
this.tasksService.cancelPending()
clearInterval(this.autoRefreshInterval)
}
dismissTask(task: PaperlessTask) {
@@ -96,19 +185,30 @@ export class TasksComponent
get currentTasks(): PaperlessTask[] {
let tasks: PaperlessTask[] = []
switch (this.activeTab) {
case 'queued':
case TaskTab.Queued:
tasks = this.tasksService.queuedFileTasks
break
case 'started':
case TaskTab.Started:
tasks = this.tasksService.startedFileTasks
break
case 'completed':
case TaskTab.Completed:
tasks = this.tasksService.completedFileTasks
break
case 'failed':
case TaskTab.Failed:
tasks = this.tasksService.failedFileTasks
break
}
if (this._filterText.length) {
tasks = tasks.filter((t) => {
if (this.filterTargetID == TaskFilterTargetID.Name) {
return t.task_file_name
.toLowerCase()
.includes(this._filterText.toLowerCase())
} else if (this.filterTargetID == TaskFilterTargetID.Result) {
return t.result.toLowerCase().includes(this._filterText.toLowerCase())
}
})
}
return tasks
}
@@ -125,31 +225,37 @@ export class TasksComponent
this.selectedTasks.clear()
}
duringTabChange(navID: number) {
duringTabChange() {
this.page = 1
}
beforeTabChange() {
this.resetFilter()
this.filterTargetID = TaskFilterTargetID.Name
}
get activeTabLocalized(): string {
switch (this.activeTab) {
case 'queued':
case TaskTab.Queued:
return $localize`queued`
case 'started':
case TaskTab.Started:
return $localize`started`
case 'completed':
case TaskTab.Completed:
return $localize`completed`
case 'failed':
case TaskTab.Failed:
return $localize`failed`
}
}
toggleAutoRefresh(): void {
if (this.autoRefreshInterval) {
clearInterval(this.autoRefreshInterval)
this.autoRefreshInterval = null
} else {
this.autoRefreshInterval = setInterval(() => {
this.tasksService.reload()
}, 5000)
public resetFilter() {
this._filterText = ''
}
filterInputKeyup(event: KeyboardEvent) {
if (event.key == 'Enter') {
this._filterText = (event.target as HTMLInputElement).value
} else if (event.key === 'Escape') {
this.resetFilter()
}
}
}

View File

@@ -38,7 +38,7 @@
</tr>
</thead>
<tbody>
@if (isLoading) {
@if (loading) {
<tr>
<td colspan="5">
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
@@ -47,15 +47,20 @@
</tr>
}
@for (document of documentsInTrash; track document.id) {
<tr (click)="toggleSelected(document); $event.stopPropagation();">
<tr (click)="toggleSelected(document); $event.stopPropagation();" (mouseleave)="popupPreview.close()" class="data-row fade" [class.show]="show">
<td>
<div class="form-check m-0 ms-2 me-n2">
<input type="checkbox" class="form-check-input" id="{{document.id}}" [checked]="selectedDocuments.has(document.id)" (click)="toggleSelected(document); $event.stopPropagation();">
<label class="form-check-label" for="{{document.id}}"></label>
</div>
</td>
<td scope="row">{{ document.title }}</td>
<td scope="row" i18n>{{ getDaysRemaining(document) }} days</td>
<td scope="row">
{{ document.title }}
<pngx-preview-popup [document]="document" linkClasses="btn btn-sm btn-link" #popupPreview>
<i-bs name="eye"></i-bs>
</pngx-preview-popup>
</td>
<td scope="row" class="d-none d-sm-table-cell" i18n>{{ getDaysRemaining(document) }} days</td>
<td scope="row">
<div class="btn-group d-block d-sm-none">
<div ngbDropdown container="body" class="d-inline-block">
@@ -83,7 +88,7 @@
</table>
</div>
@if (!isLoading) {
@if (!loading) {
<div class="d-flex mb-2">
<div>
<ng-container i18n>{totalDocuments, plural, =1 {One document in trash} other {{{totalDocuments || 0}} total documents in trash}}</ng-container>

View File

@@ -0,0 +1,4 @@
// hide caret on mobile dropdown
.d-block.d-sm-none .dropdown-toggle::after {
display: none;
}

View File

@@ -1,21 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { TrashComponent } from './trash.component'
import { HttpClientTestingModule } from '@angular/common/http/testing'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import { Router } from '@angular/router'
import {
NgbModal,
NgbPaginationModule,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TrashService } from 'src/app/services/trash.service'
import { of, throwError } from 'rxjs'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { By } from '@angular/platform-browser'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { ToastService } from 'src/app/services/toast.service'
import { TrashService } from 'src/app/services/trash.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { TrashComponent } from './trash.component'
const documentsInTrash = [
{
@@ -38,15 +39,10 @@ describe('TrashComponent', () => {
let trashService: TrashService
let modalService: NgbModal
let toastService: ToastService
let router: Router
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TrashComponent,
PageHeaderComponent,
ConfirmDialogComponent,
SafeHtmlPipe,
],
imports: [
HttpClientTestingModule,
FormsModule,
@@ -54,6 +50,10 @@ describe('TrashComponent', () => {
NgbPopoverModule,
NgbPaginationModule,
NgxBootstrapIconsModule.pick(allIcons),
TrashComponent,
PageHeaderComponent,
ConfirmDialogComponent,
SafeHtmlPipe,
],
}).compileComponents()
@@ -61,11 +61,13 @@ describe('TrashComponent', () => {
trashService = TestBed.inject(TrashService)
modalService = TestBed.inject(NgbModal)
toastService = TestBed.inject(ToastService)
router = TestBed.inject(Router)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should call correct service method on reload', () => {
jest.useFakeTimers()
const trashSpy = jest.spyOn(trashService, 'getTrash')
trashSpy.mockReturnValue(
of({
@@ -75,6 +77,7 @@ describe('TrashComponent', () => {
})
)
component.reload()
jest.advanceTimersByTime(100)
expect(trashSpy).toHaveBeenCalled()
expect(component.documentsInTrash).toEqual(documentsInTrash)
})
@@ -161,6 +164,22 @@ describe('TrashComponent', () => {
expect(restoreSpy).toHaveBeenCalledWith([1, 2])
})
it('should offer link to restored document', () => {
let toasts
const navigateSpy = jest.spyOn(router, 'navigate')
toastService.getToasts().subscribe((allToasts) => {
toasts = [...allToasts]
})
jest.spyOn(trashService, 'restoreDocuments').mockReturnValue(of('OK'))
component.restore(documentsInTrash[0])
expect(toasts.length).toEqual(1)
toasts[0].action()
expect(navigateSpy).toHaveBeenCalledWith([
'documents',
documentsInTrash[0].id,
])
})
it('should support toggle all items in view', () => {
component.documentsInTrash = documentsInTrash
expect(component.selectedDocuments.size).toEqual(0)

View File

@@ -1,49 +1,74 @@
import { Component, OnDestroy } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router'
import {
NgbDropdownModule,
NgbModal,
NgbPaginationModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { delay, takeUntil, tap } from 'rxjs'
import { Document } from 'src/app/data/document'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { TrashService } from 'src/app/services/trash.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { Subject, takeUntil } from 'rxjs'
import { SettingsService } from 'src/app/services/settings.service'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({
selector: 'pngx-trash',
templateUrl: './trash.component.html',
styleUrl: './trash.component.scss',
imports: [
PageHeaderComponent,
PreviewPopupComponent,
FormsModule,
ReactiveFormsModule,
NgbDropdownModule,
NgbPaginationModule,
NgxBootstrapIconsModule,
],
})
export class TrashComponent implements OnDestroy {
export class TrashComponent
extends LoadingComponentWithPermissions
implements OnDestroy
{
public documentsInTrash: Document[] = []
public selectedDocuments: Set<number> = new Set()
public allToggled: boolean = false
public page: number = 1
public totalDocuments: number
public isLoading: boolean = false
unsubscribeNotifier: Subject<void> = new Subject()
constructor(
private trashService: TrashService,
private toastService: ToastService,
private modalService: NgbModal,
private settingsService: SettingsService
private settingsService: SettingsService,
private router: Router
) {
super()
this.reload()
}
ngOnDestroy() {
this.unsubscribeNotifier.next()
this.unsubscribeNotifier.complete()
}
reload() {
this.isLoading = true
this.trashService.getTrash(this.page).subscribe((r) => {
this.documentsInTrash = r.results
this.totalDocuments = r.count
this.isLoading = false
this.selectedDocuments.clear()
})
this.loading = true
this.trashService
.getTrash(this.page)
.pipe(
tap((r) => {
this.documentsInTrash = r.results
this.totalDocuments = r.count
this.selectedDocuments.clear()
this.loading = false
}),
delay(100)
)
.subscribe(() => {
this.show = true
})
}
delete(document: Document) {
@@ -110,7 +135,14 @@ export class TrashComponent implements OnDestroy {
restore(document: Document) {
this.trashService.restoreDocuments([document.id]).subscribe({
next: () => {
this.toastService.showInfo($localize`Document restored`)
this.toastService.show({
content: $localize`Document restored`,
delay: 5000,
actionName: $localize`Open document`,
action: () => {
this.router.navigate(['documents', document.id])
},
})
this.reload()
},
error: (err) => {

View File

@@ -1,4 +1,5 @@
import { DatePipe } from '@angular/common'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
ComponentFixture,
@@ -6,22 +7,13 @@ import {
fakeAsync,
tick,
} from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterTestingModule } from '@angular/router/testing'
import {
NgbModule,
NgbAlertModule,
NgbModal,
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { throwError, of } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import { IfOwnerDirective } from 'src/app/directives/if-owner.directive'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
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'
@@ -30,21 +22,7 @@ import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { GroupEditDialogComponent } from '../../common/edit-dialog/group-edit-dialog/group-edit-dialog.component'
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { CheckComponent } from '../../common/input/check/check.component'
import { NumberComponent } from '../../common/input/number/number.component'
import { PasswordComponent } from '../../common/input/password/password.component'
import { PermissionsGroupComponent } from '../../common/input/permissions/permissions-group/permissions-group.component'
import { PermissionsUserComponent } from '../../common/input/permissions/permissions-user/permissions-user.component'
import { SelectComponent } from '../../common/input/select/select.component'
import { TagsComponent } from '../../common/input/tags/tags.component'
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 { User } from 'src/app/data/user'
import { Group } from 'src/app/data/group'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
const users = [
{ id: 1, username: 'user1', is_superuser: false },
@@ -67,33 +45,7 @@ describe('UsersAndGroupsComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
UsersAndGroupsComponent,
SettingsComponent,
PageHeaderComponent,
IfPermissionsDirective,
CustomDatePipe,
ConfirmDialogComponent,
CheckComponent,
SafeHtmlPipe,
SelectComponent,
TextComponent,
PasswordComponent,
NumberComponent,
TagsComponent,
PermissionsUserComponent,
PermissionsGroupComponent,
IfOwnerDirective,
],
imports: [
NgbModule,
RouterTestingModule.withRoutes(routes),
FormsModule,
ReactiveFormsModule,
NgbAlertModule,
NgSelectModule,
NgxBootstrapIconsModule.pick(allIcons),
],
imports: [NgxBootstrapIconsModule.pick(allIcons)],
providers: [
CustomDatePipe,
DatePipe,

View File

@@ -1,23 +1,31 @@
import { Component, OnDestroy, OnInit } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, first, takeUntil } from 'rxjs'
import { Group } from 'src/app/data/group'
import { User } from 'src/app/data/user'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
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'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-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 { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { SettingsService } from 'src/app/services/settings.service'
@Component({
selector: 'pngx-users-groups',
templateUrl: './users-groups.component.html',
styleUrls: ['./users-groups.component.scss'],
imports: [
PageHeaderComponent,
IfPermissionsDirective,
NgxBootstrapIconsModule,
],
})
export class UsersAndGroupsComponent
extends ComponentWithPermissions

View File

@@ -1,4 +1,4 @@
<nav class="navbar navbar-dark sticky-top bg-primary flex-md-nowrap p-0 shadow-sm">
<nav class="navbar navbar-dark fixed-top bg-primary flex-md-nowrap p-0 shadow-sm">
<button class="navbar-toggler d-md-none collapsed border-0" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"
(click)="isMenuCollapsed = !isMenuCollapsed">
@@ -199,6 +199,13 @@
<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.SavedView }">
<a class="nav-link" routerLink="savedviews" routerLinkActive="active" (click)="closeMenu()"
ngbPopover="Saved Views" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end"
container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<i-bs class="me-1" name="window-stack"></i-bs><span>&nbsp;<ng-container i18n>Saved Views</ng-container></span>
</a>
</li>
<li class="nav-item app-link"
*pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Workflow }"
tourAnchor="tour.workflows">
@@ -327,7 +334,7 @@
</a>
}
} @else {
<a class="small text-decoration-none" routerLink="/settings" fragment="update-checking"
<a *pngxIfPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" 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>

View File

@@ -1,6 +1,3 @@
@import "node_modules/bootstrap/scss/functions";
@import "node_modules/bootstrap/scss/variables";
/*
* Sidebar
*/
@@ -15,6 +12,7 @@
overflow-y: auto;
--pngx-sidebar-width: 100%;
max-width: var(--pngx-sidebar-width);
transition: all .2s ease;
.sidebar-heading .spinner-border {
width: 0.8em;
@@ -37,8 +35,6 @@
@media (min-width: 2400px) {
--pngx-sidebar-width: 8.33333333%;
}
transition: all .2s ease;
}
@media (max-width: 767.98px) {
.sidebar {
@@ -48,6 +44,13 @@
main {
transition: all .2s ease;
padding-top: 110px;
}
@media (min-width: 768px) {
main {
padding-top: 56px;
}
}
.sidebar-slim-toggler {

View File

@@ -1,43 +1,43 @@
import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import {
HttpTestingController,
provideHttpClientTesting,
} from '@angular/common/http/testing'
import { AppFrameComponent } from './app-frame.component'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { NgbModal, NgbModalModule, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { BrowserModule } from '@angular/platform-browser'
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/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 { BrowserModule } from '@angular/platform-browser'
import { ActivatedRoute, Router } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import { NgbModal, NgbModalModule, NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { ToastService } from 'src/app/services/toast.service'
import { routes } from 'src/app/app-routing.module'
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 {
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 { PermissionsService } from 'src/app/services/permissions.service'
import { RemoteVersionService } from 'src/app/services/rest/remote-version.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SearchService } from 'src/app/services/rest/search.service'
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 { SavedView } from 'src/app/data/saved-view'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import { AppFrameComponent } from './app-frame.component'
import { GlobalSearchComponent } from './global-search/global-search.component'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
const saved_views = [
{
@@ -95,11 +95,6 @@ describe('AppFrameComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [
AppFrameComponent,
IfPermissionsDirective,
GlobalSearchComponent,
],
imports: [
BrowserModule,
RouterTestingModule.withRoutes(routes),
@@ -109,6 +104,9 @@ describe('AppFrameComponent', () => {
DragDropModule,
NgbModalModule,
NgxBootstrapIconsModule.pick(allIcons),
AppFrameComponent,
IfPermissionsDirective,
GlobalSearchComponent,
],
providers: [
SettingsService,
@@ -343,6 +341,7 @@ describe('AppFrameComponent', () => {
component.editProfile()
expect(modalSpy).toHaveBeenCalledWith(ProfileEditDialogComponent, {
backdrop: 'static',
size: 'xl',
})
})

View File

@@ -1,45 +1,72 @@
import {
CdkDragDrop,
CdkDragEnd,
CdkDragStart,
DragDropModule,
moveItemInArray,
} from '@angular/cdk/drag-drop'
import { NgClass } from '@angular/common'
import { Component, HostListener, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { ActivatedRoute, Router, RouterModule } from '@angular/router'
import {
NgbCollapseModule,
NgbDropdownModule,
NgbModal,
NgbNavModule,
NgbPopoverModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
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 { 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 { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import {
DjangoMessageLevel,
DjangoMessagesService,
} from 'src/app/services/django-messages.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { environment } from 'src/environments/environment'
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import {
RemoteVersionService,
AppRemoteVersion,
} from 'src/app/services/rest/remote-version.service'
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/ui-settings'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from 'src/app/services/permissions.service'
import { SavedView } from 'src/app/data/saved-view'
import {
CdkDragStart,
CdkDragEnd,
CdkDragDrop,
moveItemInArray,
} from '@angular/cdk/drag-drop'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
AppRemoteVersion,
RemoteVersionService,
} from 'src/app/services/rest/remote-version.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
import { DocumentDetailComponent } from '../document-detail/document-detail.component'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { GlobalSearchComponent } from './global-search/global-search.component'
@Component({
selector: 'pngx-app-frame',
templateUrl: './app-frame.component.html',
styleUrls: ['./app-frame.component.scss'],
imports: [
GlobalSearchComponent,
DocumentTitlePipe,
IfPermissionsDirective,
RouterModule,
NgClass,
NgbDropdownModule,
NgbPopoverModule,
NgbCollapseModule,
NgbNavModule,
NgxBootstrapIconsModule,
DragDropModule,
TourNgBootstrapModule,
],
})
export class AppFrameComponent
extends ComponentWithPermissions
@@ -81,7 +108,14 @@ export class AppFrameComponent
if (this.settingsService.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)) {
this.checkForUpdates()
}
this.tasksService.reload()
if (
this.permissionsService.currentUserCan(
PermissionAction.View,
PermissionType.PaperlessTask
)
) {
this.tasksService.reload()
}
this.djangoMessagesService.get().forEach((message) => {
switch (message.level) {
@@ -136,6 +170,7 @@ export class AppFrameComponent
editProfile() {
this.modalService.open(ProfileEditDialogComponent, {
backdrop: 'static',
size: 'xl',
})
this.closeMenu()
}

View File

@@ -49,7 +49,7 @@
[disabled]="disablePrimaryButton(type, item)"
(mouseenter)="onButtonHover($event)">
@if (type === DataType.Document) {
<i-bs width="1em" height="1em" name="box-arrow-in-right"></i-bs>
<i-bs width="1em" height="1em" name="file-earmark-richtext"></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>
@@ -72,7 +72,7 @@
<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="box-arrow-in-right"></i-bs>
<i-bs width="1em" height="1em" name="file-earmark-richtext"></i-bs>
<span>&nbsp;<ng-container i18n>Open</ng-container></span>
}
</button>

View File

@@ -1,12 +1,13 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ElementRef } from '@angular/core'
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 { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router'
import {
NgbDropdownModule,
@@ -14,11 +15,9 @@ import {
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 { provideHttpClientTesting } from '@angular/common/http/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { DataType } from 'src/app/data/datatype'
import {
FILTER_FULLTEXT_QUERY,
FILTER_HAS_CORRESPONDENT_ANY,
@@ -27,20 +26,21 @@ import {
FILTER_HAS_TAGS_ALL,
FILTER_TITLE_CONTENT,
} 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'
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
import { SettingsService } from 'src/app/services/settings.service'
import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SearchService } from 'src/app/services/rest/search.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
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 { 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 { 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 { GlobalSearchComponent } from './global-search.component'
const searchResults = {
total: 11,
@@ -138,13 +138,13 @@ describe('GlobalSearchComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GlobalSearchComponent],
imports: [
NgbModalModule,
NgbDropdownModule,
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule.pick(allIcons),
GlobalSearchComponent,
],
providers: [
provideHttpClient(withInterceptorsFromDi()),

View File

@@ -1,14 +1,23 @@
import { NgTemplateOutlet } from '@angular/common'
import {
Component,
ViewChild,
ElementRef,
ViewChildren,
QueryList,
OnInit,
QueryList,
ViewChild,
ViewChildren,
} from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { Router } from '@angular/router'
import { NgbDropdown, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
import { Subject, debounceTime, distinctUntilChanged, filter, map } from 'rxjs'
import {
NgbDropdown,
NgbDropdownModule,
NgbModal,
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, debounceTime, distinctUntilChanged, filter } from 'rxjs'
import { DataType } from 'src/app/data/datatype'
import {
FILTER_FULLTEXT_QUERY,
FILTER_HAS_CORRESPONDENT_ANY,
@@ -17,19 +26,23 @@ import {
FILTER_HAS_TAGS_ALL,
FILTER_TITLE_CONTENT,
} from 'src/app/data/filter-rule-type'
import { DataType } from 'src/app/data/datatype'
import { ObjectWithId } from 'src/app/data/object-with-id'
import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { HotKeyService } from 'src/app/services/hot-key.service'
import {
PermissionsService,
PermissionAction,
PermissionsService,
} 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 { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { paramsFromViewState } from 'src/app/utils/query-params'
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'
@@ -41,15 +54,19 @@ import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage
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'
import { paramsFromViewState } from 'src/app/utils/query-params'
import { SettingsService } from 'src/app/services/settings.service'
import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
@Component({
selector: 'pngx-global-search',
templateUrl: './global-search.component.html',
styleUrl: './global-search.component.scss',
imports: [
CustomDatePipe,
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule,
NgbDropdownModule,
NgTemplateOutlet,
],
})
export class GlobalSearchComponent implements OnInit {
public DataType = DataType
@@ -89,7 +106,6 @@ export class GlobalSearchComponent implements OnInit {
this.queryDebounce
.pipe(
debounceTime(400),
map((query) => query?.trim()),
filter((query) => !query?.length || query?.length > 2),
distinctUntilChanged()
)
@@ -109,7 +125,7 @@ export class GlobalSearchComponent implements OnInit {
private search(query: string) {
this.loading = true
this.searchService.globalSearch(query).subscribe((results) => {
this.searchService.globalSearch(query.trim()).subscribe((results) => {
this.searchResults = results
this.loading = false
this.resultsDropdown.open()

View File

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

View File

@@ -1,9 +1,11 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
@Component({
selector: 'pngx-clearable-badge',
templateUrl: './clearable-badge.component.html',
styleUrls: ['./clearable-badge.component.scss'],
imports: [NgxBootstrapIconsModule],
})
export class ClearableBadgeComponent {
constructor() {}

View File

@@ -1,8 +1,8 @@
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'
import { ConfirmButtonComponent } from './confirm-button.component'
describe('ConfirmButtonComponent', () => {
let component: ConfirmButtonComponent
@@ -10,8 +10,11 @@ describe('ConfirmButtonComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ConfirmButtonComponent],
imports: [NgbPopoverModule, NgxBootstrapIconsModule.pick(allIcons)],
imports: [
NgbPopoverModule,
NgxBootstrapIconsModule.pick(allIcons),
ConfirmButtonComponent,
],
}).compileComponents()
fixture = TestBed.createComponent(ConfirmButtonComponent)

View File

@@ -5,12 +5,14 @@ import {
Output,
ViewChild,
} from '@angular/core'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { NgbPopover, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
@Component({
selector: 'pngx-confirm-button',
templateUrl: './confirm-button.component.html',
styleUrl: './confirm-button.component.scss',
imports: [NgbPopoverModule, NgxBootstrapIconsModule],
})
export class ConfirmButtonComponent {
@Input()

View File

@@ -1,14 +1,8 @@
import {
ComponentFixture,
TestBed,
discardPeriodicTasks,
fakeAsync,
tick,
} from '@angular/core/testing'
import { ConfirmDialogComponent } from './confirm-dialog.component'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { Subject } from 'rxjs'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { ConfirmDialogComponent } from './confirm-dialog.component'
describe('ConfirmDialogComponent', () => {
let component: ConfirmDialogComponent
@@ -17,9 +11,8 @@ describe('ConfirmDialogComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
declarations: [ConfirmDialogComponent, SafeHtmlPipe],
providers: [NgbActiveModal, SafeHtmlPipe],
imports: [],
imports: [ConfirmDialogComponent, SafeHtmlPipe],
}).compileComponents()
modal = TestBed.inject(NgbActiveModal)

View File

@@ -1,14 +1,20 @@
import { DecimalPipe } from '@angular/common'
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { interval, Subject, take } from 'rxjs'
import { Subject } from 'rxjs'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({
selector: 'pngx-confirm-dialog',
templateUrl: './confirm-dialog.component.html',
styleUrls: ['./confirm-dialog.component.scss'],
imports: [DecimalPipe, SafeHtmlPipe],
})
export class ConfirmDialogComponent {
constructor(public activeModal: NgbActiveModal) {}
export class ConfirmDialogComponent extends LoadingComponentWithPermissions {
constructor(public activeModal: NgbActiveModal) {
super()
}
@Output()
public confirmClicked = new EventEmitter()

View File

@@ -1,6 +1,6 @@
.pdf-viewer-container {
background-color: gray;
height: 350px;
height: 550px;
pdf-viewer {
width: 100%;

View File

@@ -1,12 +1,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { DeletePagesConfirmDialogComponent } from './delete-pages-confirm-dialog.component'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { PdfViewerComponent } from 'ng2-pdf-viewer'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { DeletePagesConfirmDialogComponent } from './delete-pages-confirm-dialog.component'
describe('DeletePagesConfirmDialogComponent', () => {
let component: DeletePagesConfirmDialogComponent
@@ -14,11 +13,12 @@ describe('DeletePagesConfirmDialogComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [DeletePagesConfirmDialogComponent, PdfViewerComponent],
declarations: [],
imports: [
NgxBootstrapIconsModule.pick(allIcons),
FormsModule,
ReactiveFormsModule,
DeletePagesConfirmDialogComponent,
],
providers: [
NgbActiveModal,

View File

@@ -1,13 +1,20 @@
import { Component, TemplateRef, ViewChild } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import {
PDFDocumentProxy,
PdfViewerComponent,
PdfViewerModule,
} from 'ng2-pdf-viewer'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
import { PDFDocumentProxy, PdfViewerComponent } from 'ng2-pdf-viewer'
@Component({
selector: 'pngx-delete-pages-confirm-dialog',
templateUrl: './delete-pages-confirm-dialog.component.html',
styleUrl: './delete-pages-confirm-dialog.component.scss',
imports: [PdfViewerModule, FormsModule, ReactiveFormsModule, SafeHtmlPipe],
})
export class DeletePagesConfirmDialogComponent extends ConfirmDialogComponent {
public documentID: number

View File

@@ -1,12 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { MergeConfirmDialogComponent } from './merge-confirm-dialog.component'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { DocumentService } from 'src/app/services/rest/document.service'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { MergeConfirmDialogComponent } from './merge-confirm-dialog.component'
describe('MergeConfirmDialogComponent', () => {
let component: MergeConfirmDialogComponent
@@ -15,11 +15,11 @@ describe('MergeConfirmDialogComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [MergeConfirmDialogComponent],
imports: [
NgxBootstrapIconsModule.pick(allIcons),
ReactiveFormsModule,
FormsModule,
MergeConfirmDialogComponent,
],
providers: [
NgbActiveModal,

View File

@@ -1,16 +1,28 @@
import {
CdkDragDrop,
DragDropModule,
moveItemInArray,
} from '@angular/cdk/drag-drop'
import { Component, OnInit } from '@angular/core'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { DocumentService } from 'src/app/services/rest/document.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'
import { Subject, takeUntil } from 'rxjs'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { takeUntil } from 'rxjs'
import { Document } from 'src/app/data/document'
import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
@Component({
selector: 'pngx-merge-confirm-dialog',
templateUrl: './merge-confirm-dialog.component.html',
styleUrl: './merge-confirm-dialog.component.scss',
imports: [
DragDropModule,
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule,
],
})
export class MergeConfirmDialogComponent
extends ConfirmDialogComponent
@@ -25,8 +37,6 @@ export class MergeConfirmDialogComponent
public metadataDocumentID: number = -1
private unsubscribeNotifier: Subject<any> = new Subject()
constructor(
activeModal: NgbActiveModal,
private documentService: DocumentService,

View File

@@ -1,10 +1,10 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { RotateConfirmDialogComponent } from './rotate-confirm-dialog.component'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { RotateConfirmDialogComponent } from './rotate-confirm-dialog.component'
describe('RotateConfirmDialogComponent', () => {
let component: RotateConfirmDialogComponent
@@ -12,8 +12,11 @@ describe('RotateConfirmDialogComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RotateConfirmDialogComponent, SafeHtmlPipe],
imports: [NgxBootstrapIconsModule.pick(allIcons)],
imports: [
NgxBootstrapIconsModule.pick(allIcons),
RotateConfirmDialogComponent,
SafeHtmlPipe,
],
providers: [
NgbActiveModal,
SafeHtmlPipe,

View File

@@ -1,12 +1,16 @@
import { NgStyle } from '@angular/common'
import { Component } from '@angular/core'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
@Component({
selector: 'pngx-rotate-confirm-dialog',
templateUrl: './rotate-confirm-dialog.component.html',
styleUrl: './rotate-confirm-dialog.component.scss',
imports: [NgStyle, NgxBootstrapIconsModule, SafeHtmlPipe],
})
export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
public documentID: number

View File

@@ -6,7 +6,7 @@
<div class="modal-body">
<p>{{message}}</p>
<div class="row mb-2">
<div class="col-8">
<div class="col-7">
<div class="input-group input-group-sm">
<div class="input-group-text" i18n>Page</div>
<input class="form-control" type="number" min="1" [(ngModel)]="page" />
@@ -21,7 +21,7 @@
</pdf-viewer>
</div>
</div>
<div class="col-4">
<div class="col-5">
<div class="d-grid">
<button class="btn btn-sm btn-primary" (click)="addSplit()" [disabled]="!canSplit">
<i-bs name="plus-circle"></i-bs>&nbsp;
@@ -44,12 +44,12 @@
</ul>
</div>
</div>
<div class="form-check form-switch mt-4">
</div>
<div class="modal-footer">
<div class="form-check form-switch me-auto">
<input class="form-check-input" type="checkbox" role="switch" id="deleteOriginalSwitch" [(ngModel)]="deleteOriginal" [disabled]="!userOwnsDocument">
<label class="form-check-label" for="deleteOriginalSwitch" i18n>Delete original document after successful split</label>
</div>
</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>

View File

@@ -1,6 +1,6 @@
.pdf-viewer-container {
background-color: gray;
height: 350px;
height: 500px;
pdf-viewer {
width: 100%;

View File

@@ -1,14 +1,14 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { SplitConfirmDialogComponent } from './split-confirm-dialog.component'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ReactiveFormsModule, FormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { DocumentService } from 'src/app/services/rest/document.service'
import { PdfViewerModule } from 'ng2-pdf-viewer'
import { of } from 'rxjs'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SplitConfirmDialogComponent } from './split-confirm-dialog.component'
describe('SplitConfirmDialogComponent', () => {
let component: SplitConfirmDialogComponent
@@ -17,12 +17,12 @@ describe('SplitConfirmDialogComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SplitConfirmDialogComponent],
imports: [
NgxBootstrapIconsModule.pick(allIcons),
ReactiveFormsModule,
FormsModule,
PdfViewerModule,
SplitConfirmDialogComponent,
],
providers: [
NgbActiveModal,

View File

@@ -1,15 +1,23 @@
import { Component, OnInit } from '@angular/core'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
import { Document } from 'src/app/data/document'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { DocumentService } from 'src/app/services/rest/document.service'
import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Document } from 'src/app/data/document'
import { PermissionsService } from 'src/app/services/permissions.service'
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../confirm-dialog.component'
@Component({
selector: 'pngx-split-confirm-dialog',
templateUrl: './split-confirm-dialog.component.html',
styleUrl: './split-confirm-dialog.component.scss',
imports: [
FormsModule,
ReactiveFormsModule,
NgxBootstrapIconsModule,
PdfViewerModule,
],
})
export class SplitConfirmDialogComponent
extends ConfirmDialogComponent

View File

@@ -1,12 +1,12 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { of } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { DisplayField, Document } from 'src/app/data/document'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { CustomFieldDisplayComponent } from './custom-field-display.component'
import { DisplayField, Document } from 'src/app/data/document'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
const customFields: CustomField[] = [
{ id: 1, name: 'Field 1', data_type: CustomFieldDataType.String },
@@ -17,7 +17,11 @@ const customFields: CustomField[] = [
name: 'Field 4',
data_type: CustomFieldDataType.Select,
extra_data: {
select_options: ['Option 1', 'Option 2', 'Option 3'],
select_options: [
{ label: 'Option 1', id: 'abc-123' },
{ label: 'Option 2', id: 'def-456' },
{ label: 'Option 3', id: 'ghi-789' },
],
},
},
{
@@ -45,8 +49,7 @@ describe('CustomFieldDisplayComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CustomFieldDisplayComponent],
imports: [],
imports: [CustomFieldDisplayComponent],
providers: [
DocumentService,
provideHttpClient(withInterceptorsFromDi()),
@@ -131,6 +134,8 @@ describe('CustomFieldDisplayComponent', () => {
})
it('should show select value', () => {
expect(component.getSelectValue(customFields[3], 2)).toEqual('Option 3')
expect(component.getSelectValue(customFields[3], 'ghi-789')).toEqual(
'Option 3'
)
})
})

View File

@@ -1,25 +1,25 @@
import { getLocaleCurrencyCode } from '@angular/common'
import {
Component,
Inject,
Input,
LOCALE_ID,
OnDestroy,
OnInit,
} from '@angular/core'
import { Subject, takeUntil } from 'rxjs'
import { CurrencyPipe, getLocaleCurrencyCode } from '@angular/common'
import { Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
import { takeUntil } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { DisplayField, Document } from 'src/app/data/document'
import { Results } from 'src/app/data/results'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
@Component({
selector: 'pngx-custom-field-display',
templateUrl: './custom-field-display.component.html',
styleUrl: './custom-field-display.component.scss',
imports: [CustomDatePipe, CurrencyPipe, NgbTooltipModule],
})
export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
export class CustomFieldDisplayComponent
extends LoadingComponentWithPermissions
implements OnInit
{
CustomFieldDataType = CustomFieldDataType
private _document: Document
@@ -61,7 +61,6 @@ export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
private docLinkDocuments: Document[] = []
private unsubscribeNotifier: Subject<any> = new Subject()
private defaultCurrencyCode: any
constructor(
@@ -69,6 +68,7 @@ export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
private documentService: DocumentService,
@Inject(LOCALE_ID) currentLocale: string
) {
super()
this.defaultCurrencyCode = getLocaleCurrencyCode(currentLocale)
this.customFieldService.listAll().subscribe((r) => {
this.customFields = r.results
@@ -117,12 +117,7 @@ export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
return this.docLinkDocuments?.find((d) => d.id === docId)?.title
}
public getSelectValue(field: CustomField, index: number): string {
return field.extra_data.select_options[index]
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(true)
this.unsubscribeNotifier.complete()
public getSelectValue(field: CustomField, id: string): string {
return field.extra_data.select_options?.find((o) => o.id === id)?.label
}
}

View File

@@ -1,28 +1,29 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
ComponentFixture,
TestBed,
fakeAsync,
tick,
} from '@angular/core/testing'
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ToastService } from 'src/app/services/toast.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { of } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { SelectComponent } from '../input/select/select.component'
import { NgSelectModule } from '@ng-select/ng-select'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { By } from '@angular/platform-browser'
import {
NgbDropdownModule,
NgbModal,
NgbModalModule,
NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { By } from '@angular/platform-browser'
import { NgSelectModule } from '@ng-select/ng-select'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { of } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import { SelectComponent } from '../input/select/select.component'
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
const fields: CustomField[] = [
{
@@ -43,10 +44,10 @@ describe('CustomFieldsDropdownComponent', () => {
let customFieldService: CustomFieldsService
let toastService: ToastService
let modalService: NgbModal
let settingsService: SettingsService
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CustomFieldsDropdownComponent, SelectComponent],
imports: [
NgSelectModule,
FormsModule,
@@ -54,6 +55,8 @@ describe('CustomFieldsDropdownComponent', () => {
NgbModalModule,
NgbDropdownModule,
NgxBootstrapIconsModule.pick(allIcons),
CustomFieldsDropdownComponent,
SelectComponent,
],
providers: [
provideHttpClient(withInterceptorsFromDi()),
@@ -70,6 +73,8 @@ describe('CustomFieldsDropdownComponent', () => {
results: fields.concat([]),
})
)
settingsService = TestBed.inject(SettingsService)
settingsService.currentUser = { id: 1, username: 'test' }
fixture = TestBed.createComponent(CustomFieldsDropdownComponent)
component = fixture.componentInstance
fixture.detectChanges()

View File

@@ -3,31 +3,39 @@ import {
ElementRef,
EventEmitter,
Input,
OnDestroy,
Output,
QueryList,
ViewChild,
ViewChildren,
} from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { Subject, first, takeUntil } from 'rxjs'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdownModule, NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first, takeUntil } from 'rxjs'
import { CustomField, DATA_TYPE_LABELS } from 'src/app/data/custom-field'
import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { ToastService } from 'src/app/services/toast.service'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
import {
PermissionAction,
PermissionType,
PermissionsService,
} from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { ToastService } from 'src/app/services/toast.service'
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
@Component({
selector: 'pngx-custom-fields-dropdown',
templateUrl: './custom-fields-dropdown.component.html',
styleUrls: ['./custom-fields-dropdown.component.scss'],
imports: [
NgbDropdownModule,
NgxBootstrapIconsModule,
FormsModule,
ReactiveFormsModule,
],
})
export class CustomFieldsDropdownComponent implements OnDestroy {
export class CustomFieldsDropdownComponent extends LoadingComponentWithPermissions {
@Input()
documentId: number
@@ -60,8 +68,6 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
public filterText: string
private unsubscribeNotifier: Subject<any> = new Subject()
get canCreateFields(): boolean {
return this.permissionsService.currentUserCan(
PermissionAction.Add,
@@ -75,14 +81,10 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
private toastService: ToastService,
private permissionsService: PermissionsService
) {
super()
this.getFields()
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(this)
this.unsubscribeNotifier.complete()
}
private getFields() {
this.customFieldsService
.listAll()

View File

@@ -44,6 +44,8 @@
<ng-select #fieldSelects
class="paperless-input-select rounded-end"
[items]="getSelectOptionsForField(atom.field)"
bindLabel="label"
bindValue="id"
[(ngModel)]="atom.value"
[disabled]="disabled"
(mousedown)="$event.stopImmediatePropagation()"
@@ -65,7 +67,9 @@
(mousedown)="$event.stopImmediatePropagation()"
></ng-select>
<select class="w-25 form-select" [(ngModel)]="atom.operator" [disabled]="disabled">
<option *ngFor="let operator of getOperatorsForField(atom.field)" [ngValue]="operator.value">{{operator.label}}</option>
@for (operator of getOperatorsForField(atom.field); track operator.label) {
<option [ngValue]="operator.value">{{operator.label}}</option>
}
</select>
@switch (atom.operator) {
@case (CustomFieldQueryOperator.Exists) {
@@ -99,6 +103,8 @@
<ng-select
class="paperless-input-select rounded-end"
[items]="getSelectOptionsForField(atom.field)"
bindLabel="label"
bindValue="id"
[(ngModel)]="atom.value"
[disabled]="disabled"
[multiple]="true"

View File

@@ -1,14 +1,15 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import {
ComponentFixture,
fakeAsync,
TestBed,
tick,
} from '@angular/core/testing'
import {
CustomFieldQueriesModel,
CustomFieldsQueryDropdownComponent,
} from './custom-fields-query-dropdown.component'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgSelectModule } from '@ng-select/ng-select'
import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { of } from 'rxjs'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import {
@@ -16,17 +17,16 @@ import {
CustomFieldQueryLogicalOperator,
CustomFieldQueryOperatorGroups,
} from 'src/app/data/custom-field-query'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import {
CustomFieldQueryExpression,
CustomFieldQueryAtom,
CustomFieldQueryElement,
CustomFieldQueryExpression,
} from 'src/app/utils/custom-field-query-element'
import { NgSelectModule } from '@ng-select/ng-select'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import {
CustomFieldQueriesModel,
CustomFieldsQueryDropdownComponent,
} from './custom-fields-query-dropdown.component'
const customFields = [
{
@@ -39,7 +39,12 @@ const customFields = [
id: 2,
name: 'Test Select Field',
data_type: CustomFieldDataType.Select,
extra_data: { select_options: ['Option 1', 'Option 2'] },
extra_data: {
select_options: [
{ label: 'Option 1', id: 'abc-123' },
{ label: 'Option 2', id: 'def-456' },
],
},
},
]
@@ -50,13 +55,13 @@ describe('CustomFieldsQueryDropdownComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CustomFieldsQueryDropdownComponent],
imports: [
NgbDropdownModule,
NgxBootstrapIconsModule.pick(allIcons),
NgSelectModule,
FormsModule,
ReactiveFormsModule,
CustomFieldsQueryDropdownComponent,
],
providers: [
provideHttpClient(withInterceptorsFromDi()),
@@ -128,11 +133,19 @@ describe('CustomFieldsQueryDropdownComponent', () => {
id: 1,
name: 'Test Field',
data_type: CustomFieldDataType.Select,
extra_data: { select_options: ['Option 1', 'Option 2'] },
extra_data: {
select_options: [
{ label: 'Option 1', id: 'abc-123' },
{ label: 'Option 2', id: 'def-456' },
],
},
}
component.customFields = [field]
const options = component.getSelectOptionsForField(1)
expect(options).toEqual(['Option 1', 'Option 2'])
expect(options).toEqual([
{ label: 'Option 1', id: 'abc-123' },
{ label: 'Option 2', id: 'def-456' },
])
// Fallback to empty array if field is not found
const options2 = component.getSelectOptionsForField(2)

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