mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
Merge branch 'dev' into feature/slim-sidebar
This commit is contained in:
commit
a246b3b598
.build-config.json
.github/workflows
.pre-commit-config.yamlDockerfilePipfilePipfile.lockdocker-builders
docs
src-ui
package-lock.jsonpackage.json
src
app
components
app-frame
common
edit-dialog
correspondent-edit-dialog
document-type-edit-dialog
storage-path-edit-dialog
tag-edit-dialog
filterable-dropdown
document-list/filter-editor
manage/settings
data
services
environments
styles.scsssrc
documents
paperless
paperless_mail
paperless_text
paperless_tika/tests
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"qpdf": {
|
"qpdf": {
|
||||||
"version": "10.6.3"
|
"version": "11.1.1"
|
||||||
},
|
},
|
||||||
"jbig2enc": {
|
"jbig2enc": {
|
||||||
"version": "0.29",
|
"version": "0.29",
|
||||||
|
37
.github/workflows/ci.yml
vendored
37
.github/workflows/ci.yml
vendored
@ -82,6 +82,22 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
python-version: ['3.8', '3.9', '3.10']
|
python-version: ['3.8', '3.9', '3.10']
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
services:
|
||||||
|
tika:
|
||||||
|
image: ghcr.io/paperless-ngx/tika:latest
|
||||||
|
ports:
|
||||||
|
- "9998:9998/tcp"
|
||||||
|
gotenberg:
|
||||||
|
image: docker.io/gotenberg/gotenberg:7.4
|
||||||
|
ports:
|
||||||
|
- "3000:3000/tcp"
|
||||||
|
env:
|
||||||
|
# Enable Tika end to end testing
|
||||||
|
TIKA_LIVE: 1
|
||||||
|
# Enable paperless_mail testing against real server
|
||||||
|
PAPERLESS_MAIL_TEST_HOST: ${{ secrets.TEST_MAIL_HOST }}
|
||||||
|
PAPERLESS_MAIL_TEST_USER: ${{ secrets.TEST_MAIL_USER }}
|
||||||
|
PAPERLESS_MAIL_TEST_PASSWD: ${{ secrets.TEST_MAIL_PASSWD }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
@ -91,7 +107,7 @@ jobs:
|
|||||||
-
|
-
|
||||||
name: Install pipenv
|
name: Install pipenv
|
||||||
run: |
|
run: |
|
||||||
pipx install pipenv==2022.8.5
|
pipx install pipenv==2022.10.4
|
||||||
pipenv --version
|
pipenv --version
|
||||||
-
|
-
|
||||||
name: Set up Python
|
name: Set up Python
|
||||||
@ -117,11 +133,11 @@ jobs:
|
|||||||
name: Tests
|
name: Tests
|
||||||
run: |
|
run: |
|
||||||
cd src/
|
cd src/
|
||||||
pipenv run pytest
|
pipenv run pytest -rfEp
|
||||||
-
|
-
|
||||||
name: Get changed files
|
name: Get changed files
|
||||||
id: changed-files-specific
|
id: changed-files-specific
|
||||||
uses: tj-actions/changed-files@v29.0.2
|
uses: tj-actions/changed-files@v31.0.2
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
src/**
|
src/**
|
||||||
@ -484,6 +500,18 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
|
-
|
||||||
|
name: Install pipenv
|
||||||
|
run: |
|
||||||
|
pip3 install --upgrade pip setuptools wheel pipx
|
||||||
|
pipx install pipenv
|
||||||
|
-
|
||||||
|
name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
cache: "pipenv"
|
||||||
|
cache-dependency-path: 'Pipfile.lock'
|
||||||
-
|
-
|
||||||
name: Append Changelog to docs
|
name: Append Changelog to docs
|
||||||
id: append-Changelog
|
id: append-Changelog
|
||||||
@ -497,9 +525,10 @@ jobs:
|
|||||||
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
CURRENT_CHANGELOG=`tail --lines +2 changelog.md`
|
||||||
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
echo -e "$CURRENT_CHANGELOG" >> changelog-new.md
|
||||||
mv changelog-new.md changelog.md
|
mv changelog-new.md changelog.md
|
||||||
|
pipenv run pre-commit --files changelog.md
|
||||||
git config --global user.name "github-actions"
|
git config --global user.name "github-actions"
|
||||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git commit -am "Changelog ${{ steps.get_version.outputs.version }} - GHA"
|
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
|
||||||
git push origin ${{ needs.publish-release.outputs.version }}-changelog
|
git push origin ${{ needs.publish-release.outputs.version }}-changelog
|
||||||
-
|
-
|
||||||
name: Create Pull Request
|
name: Create Pull Request
|
||||||
|
2
.github/workflows/cleanup-tags.yml
vendored
2
.github/workflows/cleanup-tags.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Set up Python
|
name: Set up Python
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
-
|
-
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
|
4
.github/workflows/project-actions.yml
vendored
4
.github/workflows/project-actions.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened')
|
||||||
steps:
|
steps:
|
||||||
- name: Add issue to project and set status to ${{ env.todo }}
|
- name: Add issue to project and set status to ${{ env.todo }}
|
||||||
uses: leonsteinhaeuser/project-beta-automations@v1.3.0
|
uses: leonsteinhaeuser/project-beta-automations@v2.0.1
|
||||||
with:
|
with:
|
||||||
gh_token: ${{ secrets.GH_TOKEN }}
|
gh_token: ${{ secrets.GH_TOKEN }}
|
||||||
organization: paperless-ngx
|
organization: paperless-ngx
|
||||||
@ -44,7 +44,7 @@ jobs:
|
|||||||
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
|
||||||
steps:
|
steps:
|
||||||
- name: Add PR to project and set status to "Needs Review"
|
- name: Add PR to project and set status to "Needs Review"
|
||||||
uses: leonsteinhaeuser/project-beta-automations@v1.3.0
|
uses: leonsteinhaeuser/project-beta-automations@v2.0.1
|
||||||
with:
|
with:
|
||||||
gh_token: ${{ secrets.GH_TOKEN }}
|
gh_token: ${{ secrets.GH_TOKEN }}
|
||||||
organization: paperless-ngx
|
organization: paperless-ngx
|
||||||
|
@ -37,7 +37,7 @@ repos:
|
|||||||
exclude: "(^Pipfile\\.lock$)"
|
exclude: "(^Pipfile\\.lock$)"
|
||||||
# Python hooks
|
# Python hooks
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v3.8.2
|
rev: v3.8.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
exclude: "(migrations)"
|
exclude: "(migrations)"
|
||||||
@ -59,11 +59,11 @@ repos:
|
|||||||
args:
|
args:
|
||||||
- "--config=./src/setup.cfg"
|
- "--config=./src/setup.cfg"
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.6.0
|
rev: 22.8.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.37.3
|
rev: v2.38.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
exclude: "(migrations)"
|
exclude: "(migrations)"
|
||||||
|
@ -182,7 +182,7 @@ RUN --mount=type=bind,from=qpdf-builder,target=/qpdf \
|
|||||||
--mount=type=bind,from=pikepdf-builder,target=/pikepdf \
|
--mount=type=bind,from=pikepdf-builder,target=/pikepdf \
|
||||||
set -eux \
|
set -eux \
|
||||||
&& echo "Installing qpdf" \
|
&& echo "Installing qpdf" \
|
||||||
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf28_*.deb \
|
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf29_*.deb \
|
||||||
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/qpdf_*.deb \
|
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/qpdf_*.deb \
|
||||||
&& echo "Installing pikepdf and dependencies" \
|
&& echo "Installing pikepdf and dependencies" \
|
||||||
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pyparsing*.whl \
|
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/pyparsing*.whl \
|
||||||
|
4
Pipfile
4
Pipfile
@ -23,7 +23,7 @@ imap-tools = "*"
|
|||||||
langdetect = "*"
|
langdetect = "*"
|
||||||
pathvalidate = "*"
|
pathvalidate = "*"
|
||||||
pillow = "~=9.2"
|
pillow = "~=9.2"
|
||||||
pikepdf = "~=5.6"
|
pikepdf = "*"
|
||||||
python-gnupg = "*"
|
python-gnupg = "*"
|
||||||
python-dotenv = "*"
|
python-dotenv = "*"
|
||||||
python-dateutil = "*"
|
python-dateutil = "*"
|
||||||
@ -39,7 +39,7 @@ whitenoise = "~=6.2"
|
|||||||
watchdog = "~=2.1"
|
watchdog = "~=2.1"
|
||||||
whoosh="~=2.7"
|
whoosh="~=2.7"
|
||||||
inotifyrecursive = "~=0.3"
|
inotifyrecursive = "~=0.3"
|
||||||
ocrmypdf = "~=13.7"
|
ocrmypdf = "~=14.0"
|
||||||
tqdm = "*"
|
tqdm = "*"
|
||||||
tika = "*"
|
tika = "*"
|
||||||
# TODO: This will sadly also install daphne+dependencies,
|
# TODO: This will sadly also install daphne+dependencies,
|
||||||
|
743
Pipfile.lock
generated
743
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,7 @@ RUN set -eux \
|
|||||||
&& apt-get update --quiet \
|
&& apt-get update --quiet \
|
||||||
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
&& apt-get install --yes --quiet --no-install-recommends ${BUILD_PACKAGES} \
|
||||||
&& echo "Installing qpdf" \
|
&& echo "Installing qpdf" \
|
||||||
&& dpkg --install libqpdf28_*.deb \
|
&& dpkg --install libqpdf29_*.deb \
|
||||||
&& dpkg --install libqpdf-dev_*.deb \
|
&& dpkg --install libqpdf-dev_*.deb \
|
||||||
&& echo "Installing Python tools" \
|
&& echo "Installing Python tools" \
|
||||||
&& python3 -m pip install --no-cache-dir --upgrade \
|
&& python3 -m pip install --no-cache-dir --upgrade \
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This Dockerfile compiles the jbig2enc library
|
# This Dockerfile compiles the jbig2enc library
|
||||||
# Inputs:
|
# Inputs:
|
||||||
# - QPDF_VERSION - the version of qpdf to build a .deb.
|
# - QPDF_VERSION - the version of qpdf to build a .deb.
|
||||||
# Must be preset as a deb-src
|
# Must be present as a deb-src in bookworm
|
||||||
|
|
||||||
FROM debian:bullseye-slim as main
|
FROM debian:bullseye-slim as main
|
||||||
|
|
||||||
@ -22,27 +22,23 @@ ARG BUILD_PACKAGES="\
|
|||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
libgnutls28-dev \
|
libgnutls28-dev \
|
||||||
packaging-dev \
|
packaging-dev \
|
||||||
|
cmake \
|
||||||
zlib1g-dev"
|
zlib1g-dev"
|
||||||
|
|
||||||
WORKDIR /usr/src
|
WORKDIR /usr/src
|
||||||
|
|
||||||
# As this is an base image for a multi-stage final image
|
|
||||||
# the added size of the install is basically irrelevant
|
|
||||||
|
|
||||||
RUN set -eux \
|
RUN set -eux \
|
||||||
&& echo "Installing build tools" \
|
&& echo "Installing build tools" \
|
||||||
&& apt-get update --quiet \
|
&& apt-get update --quiet \
|
||||||
&& apt-get install --yes --quiet --no-install-recommends $BUILD_PACKAGES \
|
&& apt-get install --yes --quiet --no-install-recommends $BUILD_PACKAGES \
|
||||||
&& echo "Building qpdf" \
|
&& echo "Getting qpdf src" \
|
||||||
&& echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \
|
&& echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& mkdir qpdf \
|
&& mkdir qpdf \
|
||||||
&& cd qpdf \
|
&& cd qpdf \
|
||||||
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm \
|
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm \
|
||||||
|
&& echo "Building qpdf" \
|
||||||
&& cd qpdf-$QPDF_VERSION \
|
&& cd qpdf-$QPDF_VERSION \
|
||||||
# We don't need to build the tests (also don't run them)
|
|
||||||
&& rm -rf libtests \
|
|
||||||
&& DEBEMAIL=hello@paperless-ngx.com debchange --bpo \
|
|
||||||
&& export DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2" \
|
&& export DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2" \
|
||||||
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
|
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
|
||||||
&& ls -ahl ../*.deb \
|
&& ls -ahl ../*.deb \
|
||||||
|
@ -218,7 +218,8 @@ using the identifier which it has assigned to each document. You will end up get
|
|||||||
files like ``0000123.pdf`` in your media directory. This isn't necessarily a bad
|
files like ``0000123.pdf`` in your media directory. This isn't necessarily a bad
|
||||||
thing, because you normally don't have to access these files manually. However, if
|
thing, because you normally don't have to access these files manually. However, if
|
||||||
you wish to name your files differently, you can do that by adjusting the
|
you wish to name your files differently, you can do that by adjusting the
|
||||||
``PAPERLESS_FILENAME_FORMAT`` configuration option.
|
``PAPERLESS_FILENAME_FORMAT`` configuration option. Paperless adds the correct
|
||||||
|
file extension e.g. ``.pdf``, ``.jpg`` automatically.
|
||||||
|
|
||||||
This variable allows you to configure the filename (folders are allowed) using
|
This variable allows you to configure the filename (folders are allowed) using
|
||||||
placeholders. For example, configuring this to
|
placeholders. For example, configuring this to
|
||||||
|
@ -1,5 +1,204 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## paperless-ngx 1.9.1
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Bugfix: Fixes missing OCR mode skip_noarchive [@stumpylog](https://github.com/stumpylog) ([#1645](https://github.com/paperless-ngx/paperless-ngx/pull/1645))
|
||||||
|
- Fix reset button padding on small screens [@shamoon](https://github.com/shamoon) ([#1646](https://github.com/paperless-ngx/paperless-ngx/pull/1646))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- Improve docs re [@janis-ax](https://github.com/janis-ax) ([#1625](https://github.com/paperless-ngx/paperless-ngx/pull/1625))
|
||||||
|
- [Documentation] Add v1.9.0 changelog [@github-actions](https://github.com/github-actions) ([#1639](https://github.com/paperless-ngx/paperless-ngx/pull/1639))
|
||||||
|
|
||||||
|
### All App Changes
|
||||||
|
|
||||||
|
- Bugfix: Fixes missing OCR mode skip_noarchive [@stumpylog](https://github.com/stumpylog) ([#1645](https://github.com/paperless-ngx/paperless-ngx/pull/1645))
|
||||||
|
- Fix reset button padding on small screens [@shamoon](https://github.com/shamoon) ([#1646](https://github.com/paperless-ngx/paperless-ngx/pull/1646))
|
||||||
|
|
||||||
|
## paperless-ngx 1.9.0
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Feature: Faster, less memory barcode handling [@stumpylog](https://github.com/stumpylog) ([#1594](https://github.com/paperless-ngx/paperless-ngx/pull/1594))
|
||||||
|
- Feature: Display django-q process names [@stumpylog](https://github.com/stumpylog) ([#1567](https://github.com/paperless-ngx/paperless-ngx/pull/1567))
|
||||||
|
- Feature: Add MariaDB support [@bckelly1](https://github.com/bckelly1) ([#543](https://github.com/paperless-ngx/paperless-ngx/pull/543))
|
||||||
|
- Feature: Simplify IMAP login for UTF-8 [@stumpylog](https://github.com/stumpylog) ([#1492](https://github.com/paperless-ngx/paperless-ngx/pull/1492))
|
||||||
|
- Feature: Even better re-do of OCR [@stumpylog](https://github.com/stumpylog) ([#1451](https://github.com/paperless-ngx/paperless-ngx/pull/1451))
|
||||||
|
- Feature: document comments [@tim-vogel](https://github.com/tim-vogel) ([#1375](https://github.com/paperless-ngx/paperless-ngx/pull/1375))
|
||||||
|
- Adding date suggestions to the documents details view [@Eckii24](https://github.com/Eckii24) ([#1367](https://github.com/paperless-ngx/paperless-ngx/pull/1367))
|
||||||
|
- Feature: Event driven consumer [@stumpylog](https://github.com/stumpylog) ([#1421](https://github.com/paperless-ngx/paperless-ngx/pull/1421))
|
||||||
|
- Feature: Adds storage paths to re-tagger command [@stumpylog](https://github.com/stumpylog) ([#1446](https://github.com/paperless-ngx/paperless-ngx/pull/1446))
|
||||||
|
- Feature: Preserve original filename in metadata [@GwynHannay](https://github.com/GwynHannay) ([#1440](https://github.com/paperless-ngx/paperless-ngx/pull/1440))
|
||||||
|
- Handle tags for gmail email accounts [@sisao](https://github.com/sisao) ([#1433](https://github.com/paperless-ngx/paperless-ngx/pull/1433))
|
||||||
|
- Update redis image [@tribut](https://github.com/tribut) ([#1436](https://github.com/paperless-ngx/paperless-ngx/pull/1436))
|
||||||
|
- PAPERLESS_REDIS may be set via docker secrets [@DennisGaida](https://github.com/DennisGaida) ([#1405](https://github.com/paperless-ngx/paperless-ngx/pull/1405))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- paperless_cmd.sh: use exec to run supervisord [@lemmi](https://github.com/lemmi) ([#1617](https://github.com/paperless-ngx/paperless-ngx/pull/1617))
|
||||||
|
- Fix: Double barcode separation creates empty file [@stumpylog](https://github.com/stumpylog) ([#1596](https://github.com/paperless-ngx/paperless-ngx/pull/1596))
|
||||||
|
- Fix: Resolve issue with slow classifier [@stumpylog](https://github.com/stumpylog) ([#1576](https://github.com/paperless-ngx/paperless-ngx/pull/1576))
|
||||||
|
- Fix document comments not updating on document navigation [@shamoon](https://github.com/shamoon) ([#1566](https://github.com/paperless-ngx/paperless-ngx/pull/1566))
|
||||||
|
- Fix: Include storage paths in document exporter [@shamoon](https://github.com/shamoon) ([#1557](https://github.com/paperless-ngx/paperless-ngx/pull/1557))
|
||||||
|
- Chore: Cleanup and validate settings [@stumpylog](https://github.com/stumpylog) ([#1551](https://github.com/paperless-ngx/paperless-ngx/pull/1551))
|
||||||
|
- Bugfix: Better gunicorn settings for workers [@stumpylog](https://github.com/stumpylog) ([#1500](https://github.com/paperless-ngx/paperless-ngx/pull/1500))
|
||||||
|
- Fix actions button in tasks table [@shamoon](https://github.com/shamoon) ([#1488](https://github.com/paperless-ngx/paperless-ngx/pull/1488))
|
||||||
|
- Fix: Add missing filter rule types to SavedViewFilterRule model \& fix migrations [@shamoon](https://github.com/shamoon) ([#1463](https://github.com/paperless-ngx/paperless-ngx/pull/1463))
|
||||||
|
- Fix paperless.conf.example typo [@qcasey](https://github.com/qcasey) ([#1460](https://github.com/paperless-ngx/paperless-ngx/pull/1460))
|
||||||
|
- Bugfix: Fixes the creation of an archive file, even if noarchive was specified [@stumpylog](https://github.com/stumpylog) ([#1442](https://github.com/paperless-ngx/paperless-ngx/pull/1442))
|
||||||
|
- Fix: created_date should not be required [@shamoon](https://github.com/shamoon) ([#1412](https://github.com/paperless-ngx/paperless-ngx/pull/1412))
|
||||||
|
- Fix: dev backend testing [@stumpylog](https://github.com/stumpylog) ([#1420](https://github.com/paperless-ngx/paperless-ngx/pull/1420))
|
||||||
|
- Bugfix: Catch all exceptions during the task signals [@stumpylog](https://github.com/stumpylog) ([#1387](https://github.com/paperless-ngx/paperless-ngx/pull/1387))
|
||||||
|
- Fix: saved view page parameter [@shamoon](https://github.com/shamoon) ([#1376](https://github.com/paperless-ngx/paperless-ngx/pull/1376))
|
||||||
|
- Fix: Correct browser unsaved changes warning [@shamoon](https://github.com/shamoon) ([#1369](https://github.com/paperless-ngx/paperless-ngx/pull/1369))
|
||||||
|
- Fix: correct date pasting with other formats [@shamoon](https://github.com/shamoon) ([#1370](https://github.com/paperless-ngx/paperless-ngx/pull/1370))
|
||||||
|
- Bugfix: Allow webserver bind address to be configured [@stumpylog](https://github.com/stumpylog) ([#1358](https://github.com/paperless-ngx/paperless-ngx/pull/1358))
|
||||||
|
- Bugfix: Chain exceptions during exception handling [@stumpylog](https://github.com/stumpylog) ([#1354](https://github.com/paperless-ngx/paperless-ngx/pull/1354))
|
||||||
|
- Fix: missing tooltip translation \& filter editor wrapping [@shamoon](https://github.com/shamoon) ([#1305](https://github.com/paperless-ngx/paperless-ngx/pull/1305))
|
||||||
|
- Bugfix: Interaction between barcode and directories as tags [@stumpylog](https://github.com/stumpylog) ([#1303](https://github.com/paperless-ngx/paperless-ngx/pull/1303))
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- [Beta] Paperless-ngx v1.9.0 Release Candidate [@stumpylog](https://github.com/stumpylog) ([#1560](https://github.com/paperless-ngx/paperless-ngx/pull/1560))
|
||||||
|
- docs/configuration: Fix binary variable defaults [@erikarvstedt](https://github.com/erikarvstedt) ([#1528](https://github.com/paperless-ngx/paperless-ngx/pull/1528))
|
||||||
|
- Info about installing on subpath [@viktor-c](https://github.com/viktor-c) ([#1350](https://github.com/paperless-ngx/paperless-ngx/pull/1350))
|
||||||
|
- Docs: move scanner \& software recs to GH wiki [@shamoon](https://github.com/shamoon) ([#1482](https://github.com/paperless-ngx/paperless-ngx/pull/1482))
|
||||||
|
- Docs: Update mobile scanner section [@tooomm](https://github.com/tooomm) ([#1467](https://github.com/paperless-ngx/paperless-ngx/pull/1467))
|
||||||
|
- Adding date suggestions to the documents details view [@Eckii24](https://github.com/Eckii24) ([#1367](https://github.com/paperless-ngx/paperless-ngx/pull/1367))
|
||||||
|
- docs: scanners: add Brother ads4700w [@ocelotsloth](https://github.com/ocelotsloth) ([#1450](https://github.com/paperless-ngx/paperless-ngx/pull/1450))
|
||||||
|
- Feature: Adds storage paths to re-tagger command [@stumpylog](https://github.com/stumpylog) ([#1446](https://github.com/paperless-ngx/paperless-ngx/pull/1446))
|
||||||
|
- Changes to Redis documentation [@Zerteax](https://github.com/Zerteax) ([#1441](https://github.com/paperless-ngx/paperless-ngx/pull/1441))
|
||||||
|
- Update scanners.rst [@glassbox-sco](https://github.com/glassbox-sco) ([#1430](https://github.com/paperless-ngx/paperless-ngx/pull/1430))
|
||||||
|
- Update scanners.rst [@derlucas](https://github.com/derlucas) ([#1415](https://github.com/paperless-ngx/paperless-ngx/pull/1415))
|
||||||
|
- Bugfix: Allow webserver bind address to be configured [@stumpylog](https://github.com/stumpylog) ([#1358](https://github.com/paperless-ngx/paperless-ngx/pull/1358))
|
||||||
|
- docs: fix small typo [@tooomm](https://github.com/tooomm) ([#1352](https://github.com/paperless-ngx/paperless-ngx/pull/1352))
|
||||||
|
- [Documentation] Add v1.8.0 changelog [@github-actions](https://github.com/github-actions) ([#1298](https://github.com/paperless-ngx/paperless-ngx/pull/1298))
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
- [Beta] Paperless-ngx v1.9.0 Release Candidate [@stumpylog](https://github.com/stumpylog) ([#1560](https://github.com/paperless-ngx/paperless-ngx/pull/1560))
|
||||||
|
- paperless_cmd.sh: use exec to run supervisord [@lemmi](https://github.com/lemmi) ([#1617](https://github.com/paperless-ngx/paperless-ngx/pull/1617))
|
||||||
|
- Chore: Extended container image cleanup [@stumpylog](https://github.com/stumpylog) ([#1556](https://github.com/paperless-ngx/paperless-ngx/pull/1556))
|
||||||
|
- Chore: Smaller library images [@stumpylog](https://github.com/stumpylog) ([#1546](https://github.com/paperless-ngx/paperless-ngx/pull/1546))
|
||||||
|
- Bump tj-actions/changed-files from 24 to 29.0.2 [@dependabot](https://github.com/dependabot) ([#1493](https://github.com/paperless-ngx/paperless-ngx/pull/1493))
|
||||||
|
- Bugfix: Better gunicorn settings for workers [@stumpylog](https://github.com/stumpylog) ([#1500](https://github.com/paperless-ngx/paperless-ngx/pull/1500))
|
||||||
|
- [CI] Fix release drafter issues [@qcasey](https://github.com/qcasey) ([#1301](https://github.com/paperless-ngx/paperless-ngx/pull/1301))
|
||||||
|
- Fix: dev backend testing [@stumpylog](https://github.com/stumpylog) ([#1420](https://github.com/paperless-ngx/paperless-ngx/pull/1420))
|
||||||
|
- Chore: Exclude dependabot PRs from Project, set status to Needs Review [@qcasey](https://github.com/qcasey) ([#1397](https://github.com/paperless-ngx/paperless-ngx/pull/1397))
|
||||||
|
- Chore: Add to label PRs based on and title [@qcasey](https://github.com/qcasey) ([#1396](https://github.com/paperless-ngx/paperless-ngx/pull/1396))
|
||||||
|
- Chore: use pre-commit in the Ci workflow [@stumpylog](https://github.com/stumpylog) ([#1362](https://github.com/paperless-ngx/paperless-ngx/pull/1362))
|
||||||
|
- Chore: Fixes permissions for image tag cleanup [@stumpylog](https://github.com/stumpylog) ([#1315](https://github.com/paperless-ngx/paperless-ngx/pull/1315))
|
||||||
|
- Bump leonsteinhaeuser/project-beta-automations from 1.2.1 to 1.3.0 [@dependabot](https://github.com/dependabot) ([#1328](https://github.com/paperless-ngx/paperless-ngx/pull/1328))
|
||||||
|
- Bump tj-actions/changed-files from 23.1 to 24 [@dependabot](https://github.com/dependabot) ([#1329](https://github.com/paperless-ngx/paperless-ngx/pull/1329))
|
||||||
|
- Feature: Remove requirements.txt and use pipenv everywhere [@stumpylog](https://github.com/stumpylog) ([#1316](https://github.com/paperless-ngx/paperless-ngx/pull/1316))
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>34 changes</summary>
|
||||||
|
|
||||||
|
- Bump pikepdf from 5.5.0 to 5.6.1 [@dependabot](https://github.com/dependabot) ([#1537](https://github.com/paperless-ngx/paperless-ngx/pull/1537))
|
||||||
|
- Bump black from 22.6.0 to 22.8.0 [@dependabot](https://github.com/dependabot) ([#1539](https://github.com/paperless-ngx/paperless-ngx/pull/1539))
|
||||||
|
- Bump tqdm from 4.64.0 to 4.64.1 [@dependabot](https://github.com/dependabot) ([#1540](https://github.com/paperless-ngx/paperless-ngx/pull/1540))
|
||||||
|
- Bump pytest from 7.1.2 to 7.1.3 [@dependabot](https://github.com/dependabot) ([#1538](https://github.com/paperless-ngx/paperless-ngx/pull/1538))
|
||||||
|
- Bump tj-actions/changed-files from 24 to 29.0.2 [@dependabot](https://github.com/dependabot) ([#1493](https://github.com/paperless-ngx/paperless-ngx/pull/1493))
|
||||||
|
- Bump angular packages, jest-preset-angular in src-ui [@dependabot](https://github.com/dependabot) ([#1502](https://github.com/paperless-ngx/paperless-ngx/pull/1502))
|
||||||
|
- Bump jest-environment-jsdom from 28.1.3 to 29.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1507](https://github.com/paperless-ngx/paperless-ngx/pull/1507))
|
||||||
|
- Bump [@<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot) ([#1506](https://github.com/paperless-ngx/paperless-ngx/pull/1506))
|
||||||
|
- Bump [@<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot](https://github.com/<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot) ([#1505](https://github.com/paperless-ngx/paperless-ngx/pull/1505))
|
||||||
|
- Bump zone.js from 0.11.7 to 0.11.8 in /src-ui [@dependabot](https://github.com/dependabot) ([#1504](https://github.com/paperless-ngx/paperless-ngx/pull/1504))
|
||||||
|
- Bump ngx-color from 8.0.1 to 8.0.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#1494](https://github.com/paperless-ngx/paperless-ngx/pull/1494))
|
||||||
|
- Bump cypress from 10.3.1 to 10.7.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1496](https://github.com/paperless-ngx/paperless-ngx/pull/1496))
|
||||||
|
- Bump [@<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot](https://github.com/<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot) ([#1495](https://github.com/paperless-ngx/paperless-ngx/pull/1495))
|
||||||
|
- Bump [@<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot](https://github.com/<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot) ([#1498](https://github.com/paperless-ngx/paperless-ngx/pull/1498))
|
||||||
|
- Bump sphinx from 5.0.2 to 5.1.1 [@dependabot](https://github.com/dependabot) ([#1297](https://github.com/paperless-ngx/paperless-ngx/pull/1297))
|
||||||
|
- Chore: Bump Python dependencies [@stumpylog](https://github.com/stumpylog) ([#1445](https://github.com/paperless-ngx/paperless-ngx/pull/1445))
|
||||||
|
- Chore: Update Python deps [@stumpylog](https://github.com/stumpylog) ([#1391](https://github.com/paperless-ngx/paperless-ngx/pull/1391))
|
||||||
|
- Bump watchfiles from 0.15.0 to 0.16.1 [@dependabot](https://github.com/dependabot) ([#1285](https://github.com/paperless-ngx/paperless-ngx/pull/1285))
|
||||||
|
- Bump leonsteinhaeuser/project-beta-automations from 1.2.1 to 1.3.0 [@dependabot](https://github.com/dependabot) ([#1328](https://github.com/paperless-ngx/paperless-ngx/pull/1328))
|
||||||
|
- Bump tj-actions/changed-files from 23.1 to 24 [@dependabot](https://github.com/dependabot) ([#1329](https://github.com/paperless-ngx/paperless-ngx/pull/1329))
|
||||||
|
- Bump cypress from 10.3.0 to 10.3.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1342](https://github.com/paperless-ngx/paperless-ngx/pull/1342))
|
||||||
|
- Bump ngx-color from 7.3.3 to 8.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1343](https://github.com/paperless-ngx/paperless-ngx/pull/1343))
|
||||||
|
- Bump [@<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot](https://github.com/<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot) ([#1330](https://github.com/paperless-ngx/paperless-ngx/pull/1330))
|
||||||
|
- Bump [@<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot) ([#1341](https://github.com/paperless-ngx/paperless-ngx/pull/1341))
|
||||||
|
- Bump jest-preset-angular from 12.1.0 to 12.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1340](https://github.com/paperless-ngx/paperless-ngx/pull/1340))
|
||||||
|
- Bump concurrently from 7.2.2 to 7.3.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1326](https://github.com/paperless-ngx/paperless-ngx/pull/1326))
|
||||||
|
- Bump ng2-pdf-viewer from 9.0.0 to 9.1.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1337](https://github.com/paperless-ngx/paperless-ngx/pull/1337))
|
||||||
|
- Bump jest-environment-jsdom from 28.1.2 to 28.1.3 in /src-ui [@dependabot](https://github.com/dependabot) ([#1336](https://github.com/paperless-ngx/paperless-ngx/pull/1336))
|
||||||
|
- Bump ngx-file-drop from 13.0.0 to 14.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1331](https://github.com/paperless-ngx/paperless-ngx/pull/1331))
|
||||||
|
- Bump jest and [@<!---->types/jest in /src-ui @dependabot](https://github.com/<!---->types/jest in /src-ui @dependabot) ([#1333](https://github.com/paperless-ngx/paperless-ngx/pull/1333))
|
||||||
|
- Bump bootstrap from 5.1.3 to 5.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1327](https://github.com/paperless-ngx/paperless-ngx/pull/1327))
|
||||||
|
- Bump typescript from 4.6.4 to 4.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#1324](https://github.com/paperless-ngx/paperless-ngx/pull/1324))
|
||||||
|
- Bump ts-node from 10.8.1 to 10.9.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1325](https://github.com/paperless-ngx/paperless-ngx/pull/1325))
|
||||||
|
- Bump rxjs from 7.5.5 to 7.5.6 in /src-ui [@dependabot](https://github.com/dependabot) ([#1323](https://github.com/paperless-ngx/paperless-ngx/pull/1323))
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### All App Changes
|
||||||
|
|
||||||
|
- [Beta] Paperless-ngx v1.9.0 Release Candidate [@stumpylog](https://github.com/stumpylog) ([#1560](https://github.com/paperless-ngx/paperless-ngx/pull/1560))
|
||||||
|
- Feature: Faster, less memory barcode handling [@stumpylog](https://github.com/stumpylog) ([#1594](https://github.com/paperless-ngx/paperless-ngx/pull/1594))
|
||||||
|
- Fix: Consume directory permissions were not updated [@stumpylog](https://github.com/stumpylog) ([#1605](https://github.com/paperless-ngx/paperless-ngx/pull/1605))
|
||||||
|
- Fix: Double barcode separation creates empty file [@stumpylog](https://github.com/stumpylog) ([#1596](https://github.com/paperless-ngx/paperless-ngx/pull/1596))
|
||||||
|
- Fix: Parsing Tika documents fails with AttributeError [@stumpylog](https://github.com/stumpylog) ([#1591](https://github.com/paperless-ngx/paperless-ngx/pull/1591))
|
||||||
|
- Fix: Resolve issue with slow classifier [@stumpylog](https://github.com/stumpylog) ([#1576](https://github.com/paperless-ngx/paperless-ngx/pull/1576))
|
||||||
|
- Feature: Display django-q process names [@stumpylog](https://github.com/stumpylog) ([#1567](https://github.com/paperless-ngx/paperless-ngx/pull/1567))
|
||||||
|
- Fix document comments not updating on document navigation [@shamoon](https://github.com/shamoon) ([#1566](https://github.com/paperless-ngx/paperless-ngx/pull/1566))
|
||||||
|
- Feature: Add MariaDB support [@bckelly1](https://github.com/bckelly1) ([#543](https://github.com/paperless-ngx/paperless-ngx/pull/543))
|
||||||
|
- Fix: Include storage paths in document exporter [@shamoon](https://github.com/shamoon) ([#1557](https://github.com/paperless-ngx/paperless-ngx/pull/1557))
|
||||||
|
- Chore: Cleanup and validate settings [@stumpylog](https://github.com/stumpylog) ([#1551](https://github.com/paperless-ngx/paperless-ngx/pull/1551))
|
||||||
|
- Bump pikepdf from 5.5.0 to 5.6.1 [@dependabot](https://github.com/dependabot) ([#1537](https://github.com/paperless-ngx/paperless-ngx/pull/1537))
|
||||||
|
- Bump black from 22.6.0 to 22.8.0 [@dependabot](https://github.com/dependabot) ([#1539](https://github.com/paperless-ngx/paperless-ngx/pull/1539))
|
||||||
|
- Bump tqdm from 4.64.0 to 4.64.1 [@dependabot](https://github.com/dependabot) ([#1540](https://github.com/paperless-ngx/paperless-ngx/pull/1540))
|
||||||
|
- Bump pytest from 7.1.2 to 7.1.3 [@dependabot](https://github.com/dependabot) ([#1538](https://github.com/paperless-ngx/paperless-ngx/pull/1538))
|
||||||
|
- Bump angular packages, jest-preset-angular in src-ui [@dependabot](https://github.com/dependabot) ([#1502](https://github.com/paperless-ngx/paperless-ngx/pull/1502))
|
||||||
|
- Bump jest-environment-jsdom from 28.1.3 to 29.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1507](https://github.com/paperless-ngx/paperless-ngx/pull/1507))
|
||||||
|
- Bump [@<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.6.3 to 18.7.14 in /src-ui @dependabot) ([#1506](https://github.com/paperless-ngx/paperless-ngx/pull/1506))
|
||||||
|
- Bump [@<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot](https://github.com/<!---->angular-builders/jest from 14.0.0 to 14.0.1 in /src-ui @dependabot) ([#1505](https://github.com/paperless-ngx/paperless-ngx/pull/1505))
|
||||||
|
- Bump zone.js from 0.11.7 to 0.11.8 in /src-ui [@dependabot](https://github.com/dependabot) ([#1504](https://github.com/paperless-ngx/paperless-ngx/pull/1504))
|
||||||
|
- Bump ngx-color from 8.0.1 to 8.0.2 in /src-ui [@dependabot](https://github.com/dependabot) ([#1494](https://github.com/paperless-ngx/paperless-ngx/pull/1494))
|
||||||
|
- Bump cypress from 10.3.1 to 10.7.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1496](https://github.com/paperless-ngx/paperless-ngx/pull/1496))
|
||||||
|
- Bump [@<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot](https://github.com/<!---->cypress/schematic from 2.0.0 to 2.1.1 in /src-ui @dependabot) ([#1495](https://github.com/paperless-ngx/paperless-ngx/pull/1495))
|
||||||
|
- Bump [@<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot](https://github.com/<!---->popperjs/core from 2.11.5 to 2.11.6 in /src-ui @dependabot) ([#1498](https://github.com/paperless-ngx/paperless-ngx/pull/1498))
|
||||||
|
- Feature: Simplify IMAP login for UTF-8 [@stumpylog](https://github.com/stumpylog) ([#1492](https://github.com/paperless-ngx/paperless-ngx/pull/1492))
|
||||||
|
- Fix actions button in tasks table [@shamoon](https://github.com/shamoon) ([#1488](https://github.com/paperless-ngx/paperless-ngx/pull/1488))
|
||||||
|
- Fix: Add missing filter rule types to SavedViewFilterRule model \& fix migrations [@shamoon](https://github.com/shamoon) ([#1463](https://github.com/paperless-ngx/paperless-ngx/pull/1463))
|
||||||
|
- Feature: Even better re-do of OCR [@stumpylog](https://github.com/stumpylog) ([#1451](https://github.com/paperless-ngx/paperless-ngx/pull/1451))
|
||||||
|
- Feature: document comments [@tim-vogel](https://github.com/tim-vogel) ([#1375](https://github.com/paperless-ngx/paperless-ngx/pull/1375))
|
||||||
|
- Adding date suggestions to the documents details view [@Eckii24](https://github.com/Eckii24) ([#1367](https://github.com/paperless-ngx/paperless-ngx/pull/1367))
|
||||||
|
- Bump sphinx from 5.0.2 to 5.1.1 [@dependabot](https://github.com/dependabot) ([#1297](https://github.com/paperless-ngx/paperless-ngx/pull/1297))
|
||||||
|
- Feature: Event driven consumer [@stumpylog](https://github.com/stumpylog) ([#1421](https://github.com/paperless-ngx/paperless-ngx/pull/1421))
|
||||||
|
- Bugfix: Fixes the creation of an archive file, even if noarchive was specified [@stumpylog](https://github.com/stumpylog) ([#1442](https://github.com/paperless-ngx/paperless-ngx/pull/1442))
|
||||||
|
- Feature: Adds storage paths to re-tagger command [@stumpylog](https://github.com/stumpylog) ([#1446](https://github.com/paperless-ngx/paperless-ngx/pull/1446))
|
||||||
|
- Feature: Preserve original filename in metadata [@GwynHannay](https://github.com/GwynHannay) ([#1440](https://github.com/paperless-ngx/paperless-ngx/pull/1440))
|
||||||
|
- Handle tags for gmail email accounts [@sisao](https://github.com/sisao) ([#1433](https://github.com/paperless-ngx/paperless-ngx/pull/1433))
|
||||||
|
- Fix: should not be required [@shamoon](https://github.com/shamoon) ([#1412](https://github.com/paperless-ngx/paperless-ngx/pull/1412))
|
||||||
|
- Bugfix: Catch all exceptions during the task signals [@stumpylog](https://github.com/stumpylog) ([#1387](https://github.com/paperless-ngx/paperless-ngx/pull/1387))
|
||||||
|
- Fix: saved view page parameter [@shamoon](https://github.com/shamoon) ([#1376](https://github.com/paperless-ngx/paperless-ngx/pull/1376))
|
||||||
|
- Fix: Correct browser unsaved changes warning [@shamoon](https://github.com/shamoon) ([#1369](https://github.com/paperless-ngx/paperless-ngx/pull/1369))
|
||||||
|
- Fix: correct date pasting with other formats [@shamoon](https://github.com/shamoon) ([#1370](https://github.com/paperless-ngx/paperless-ngx/pull/1370))
|
||||||
|
- Chore: use pre-commit in the Ci workflow [@stumpylog](https://github.com/stumpylog) ([#1362](https://github.com/paperless-ngx/paperless-ngx/pull/1362))
|
||||||
|
- Bugfix: Chain exceptions during exception handling [@stumpylog](https://github.com/stumpylog) ([#1354](https://github.com/paperless-ngx/paperless-ngx/pull/1354))
|
||||||
|
- Bump watchfiles from 0.15.0 to 0.16.1 [@dependabot](https://github.com/dependabot) ([#1285](https://github.com/paperless-ngx/paperless-ngx/pull/1285))
|
||||||
|
- Bump cypress from 10.3.0 to 10.3.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1342](https://github.com/paperless-ngx/paperless-ngx/pull/1342))
|
||||||
|
- Bump ngx-color from 7.3.3 to 8.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1343](https://github.com/paperless-ngx/paperless-ngx/pull/1343))
|
||||||
|
- Bump [@<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot](https://github.com/<!---->angular/cli from 14.0.4 to 14.1.0 in /src-ui @dependabot) ([#1330](https://github.com/paperless-ngx/paperless-ngx/pull/1330))
|
||||||
|
- Bump [@<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot](https://github.com/<!---->types/node from 18.0.0 to 18.6.3 in /src-ui @dependabot) ([#1341](https://github.com/paperless-ngx/paperless-ngx/pull/1341))
|
||||||
|
- Bump jest-preset-angular from 12.1.0 to 12.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1340](https://github.com/paperless-ngx/paperless-ngx/pull/1340))
|
||||||
|
- Bump concurrently from 7.2.2 to 7.3.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1326](https://github.com/paperless-ngx/paperless-ngx/pull/1326))
|
||||||
|
- Bump ng2-pdf-viewer from 9.0.0 to 9.1.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1337](https://github.com/paperless-ngx/paperless-ngx/pull/1337))
|
||||||
|
- Bump jest-environment-jsdom from 28.1.2 to 28.1.3 in /src-ui [@dependabot](https://github.com/dependabot) ([#1336](https://github.com/paperless-ngx/paperless-ngx/pull/1336))
|
||||||
|
- Bump ngx-file-drop from 13.0.0 to 14.0.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1331](https://github.com/paperless-ngx/paperless-ngx/pull/1331))
|
||||||
|
- Bump jest and [@<!---->types/jest in /src-ui @dependabot](https://github.com/<!---->types/jest in /src-ui @dependabot) ([#1333](https://github.com/paperless-ngx/paperless-ngx/pull/1333))
|
||||||
|
- Bump bootstrap from 5.1.3 to 5.2.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#1327](https://github.com/paperless-ngx/paperless-ngx/pull/1327))
|
||||||
|
- Bump typescript from 4.6.4 to 4.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#1324](https://github.com/paperless-ngx/paperless-ngx/pull/1324))
|
||||||
|
- Bump ts-node from 10.8.1 to 10.9.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#1325](https://github.com/paperless-ngx/paperless-ngx/pull/1325))
|
||||||
|
- Bump rxjs from 7.5.5 to 7.5.6 in /src-ui [@dependabot](https://github.com/dependabot) ([#1323](https://github.com/paperless-ngx/paperless-ngx/pull/1323))
|
||||||
|
- Fix: missing tooltip translation \& filter editor wrapping [@shamoon](https://github.com/shamoon) ([#1305](https://github.com/paperless-ngx/paperless-ngx/pull/1305))
|
||||||
|
- Feature: Remove requirements.txt and use pipenv everywhere [@stumpylog](https://github.com/stumpylog) ([#1316](https://github.com/paperless-ngx/paperless-ngx/pull/1316))
|
||||||
|
- Bugfix: Interaction between barcode and directories as tags [@stumpylog](https://github.com/stumpylog) ([#1303](https://github.com/paperless-ngx/paperless-ngx/pull/1303))
|
||||||
|
|
||||||
## paperless-ngx 1.8.0
|
## paperless-ngx 1.8.0
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
@ -908,18 +908,9 @@ Update Checking
|
|||||||
###############
|
###############
|
||||||
|
|
||||||
PAPERLESS_ENABLE_UPDATE_CHECK=<bool>
|
PAPERLESS_ENABLE_UPDATE_CHECK=<bool>
|
||||||
Enable (or disable) the automatic check for available updates. This feature is disabled
|
|
||||||
by default but if it is not explicitly set Paperless-ngx will show a message about this.
|
|
||||||
|
|
||||||
If enabled, the feature works by pinging the the Github API for the latest release e.g.
|
.. note::
|
||||||
https://api.github.com/repos/paperless-ngx/paperless-ngx/releases/latest
|
|
||||||
to determine whether a new version is available.
|
|
||||||
|
|
||||||
Actual updating of the app must still be performed manually.
|
This setting was deprecated in favor of a frontend setting after v1.9.2. A one-time
|
||||||
|
migration is performed for users who have this setting set. This setting is always
|
||||||
Note that for users of thirdy-party containers e.g. linuxserver.io this notification
|
ignored if the corresponding frontend setting has been set.
|
||||||
may be 'ahead' of a new release from the third-party maintainers.
|
|
||||||
|
|
||||||
In either case, no tracking data is collected by the app in any way.
|
|
||||||
|
|
||||||
Defaults to none, which disables the feature.
|
|
||||||
|
@ -1 +1 @@
|
|||||||
myst-parser==0.17.2
|
myst-parser==0.18.1
|
||||||
|
743
src-ui/package-lock.json
generated
743
src-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,48 +13,48 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "~14.2.0",
|
"@angular/common": "~14.2.4",
|
||||||
"@angular/compiler": "~14.2.0",
|
"@angular/compiler": "~14.2.4",
|
||||||
"@angular/core": "~14.2.0",
|
"@angular/core": "~14.2.4",
|
||||||
"@angular/forms": "~14.2.0",
|
"@angular/forms": "~14.2.4",
|
||||||
"@angular/localize": "~14.2.0",
|
"@angular/localize": "~14.2.4",
|
||||||
"@angular/platform-browser": "~14.2.0",
|
"@angular/platform-browser": "~14.2.4",
|
||||||
"@angular/platform-browser-dynamic": "~14.2.0",
|
"@angular/platform-browser-dynamic": "~14.2.4",
|
||||||
"@angular/router": "~14.2.0",
|
"@angular/router": "~14.2.4",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^13.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^13.0.0",
|
||||||
"@ng-select/ng-select": "^9.0.2",
|
"@ng-select/ng-select": "^9.0.2",
|
||||||
"@ngneat/dirty-check-forms": "^3.0.2",
|
"@ngneat/dirty-check-forms": "^3.0.2",
|
||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.6",
|
||||||
"bootstrap": "^5.2.0",
|
"bootstrap": "^5.2.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"ng2-pdf-viewer": "^9.1.0",
|
"ng2-pdf-viewer": "^9.1.2",
|
||||||
"ngx-color": "^8.0.2",
|
"ngx-color": "^8.0.3",
|
||||||
"ngx-cookie-service": "^14.0.1",
|
"ngx-cookie-service": "^14.0.1",
|
||||||
"ngx-file-drop": "^14.0.1",
|
"ngx-file-drop": "^14.0.1",
|
||||||
"rxjs": "~7.5.6",
|
"rxjs": "~7.5.7",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"uuid": "^8.3.1",
|
"uuid": "^9.0.0",
|
||||||
"zone.js": "~0.11.8"
|
"zone.js": "~0.11.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/jest": "14.0.1",
|
"@angular-builders/jest": "14.0.1",
|
||||||
"@angular-devkit/build-angular": "~14.2.1",
|
"@angular-devkit/build-angular": "~14.2.4",
|
||||||
"@angular/cli": "~14.2.1",
|
"@angular/cli": "~14.2.4",
|
||||||
"@angular/compiler-cli": "~14.2.0",
|
"@angular/compiler-cli": "~14.2.4",
|
||||||
"@types/jest": "28.1.6",
|
"@types/jest": "28.1.6",
|
||||||
"@types/node": "^18.7.14",
|
"@types/node": "^18.7.23",
|
||||||
"codelyzer": "^6.0.2",
|
"codelyzer": "^6.0.2",
|
||||||
"concurrently": "7.3.0",
|
"concurrently": "7.4.0",
|
||||||
"jest": "28.1.3",
|
"jest": "28.1.3",
|
||||||
"jest-environment-jsdom": "^29.0.1",
|
"jest-environment-jsdom": "^29.1.2",
|
||||||
"jest-preset-angular": "^12.2.2",
|
"jest-preset-angular": "^12.2.2",
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"tslint": "~6.1.3",
|
"tslint": "~6.1.3",
|
||||||
"typescript": "~4.7.4",
|
"typescript": "~4.8.4",
|
||||||
"wait-on": "~6.0.1"
|
"wait-on": "~6.0.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.1.1",
|
"@cypress/schematic": "^2.1.1",
|
||||||
"cypress": "~10.7.0"
|
"cypress": "~10.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,14 +207,25 @@
|
|||||||
<li class="nav-item mt-2" [class.visually-hidden]="slimSidebarEnabled">
|
<li class="nav-item mt-2" [class.visually-hidden]="slimSidebarEnabled">
|
||||||
<div class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap">
|
<div class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap">
|
||||||
<div class="me-3">{{ versionString }}</div>
|
<div class="me-3">{{ versionString }}</div>
|
||||||
<div *ngIf="appRemoteVersion" class="version-check">
|
<div *ngIf="!settingsService.updateCheckingIsSet || appRemoteVersion" class="version-check">
|
||||||
<ng-template #updateAvailablePopContent>
|
<ng-template #updateAvailablePopContent>
|
||||||
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span>
|
<span class="small">Paperless-ngx {{ appRemoteVersion.version }} <ng-container i18n>is available.</ng-container><br/><ng-container i18n>Click to view.</ng-container></span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #updateCheckingNotEnabledPopContent>
|
<ng-template #updateCheckingNotEnabledPopContent>
|
||||||
<span class="small"><ng-container i18n>Checking for updates is disabled.</ng-container><br/><ng-container i18n>Click for more information.</ng-container></span>
|
<p class="small mb-2">
|
||||||
|
<ng-container i18n>Paperless-ngx can automatically check for updates</ng-container>
|
||||||
|
</p>
|
||||||
|
<div class="btn-group btn-group-xs flex-fill w-100">
|
||||||
|
<button class="btn btn-outline-primary" (click)="setUpdateChecking(true)">Enable</button>
|
||||||
|
<button class="btn btn-outline-secondary" (click)="setUpdateChecking(false)">Disable</button>
|
||||||
|
</div>
|
||||||
|
<p class="small mb-0 mt-2">
|
||||||
|
<a class="small text-decoration-none fst-italic" routerLink="/settings" fragment="update-checking" i18n>
|
||||||
|
How does this work?
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-container *ngIf="appRemoteVersion.feature_is_set; else updateCheckNotSet">
|
<ng-container *ngIf="settingsService.updateCheckingIsSet; else updateCheckNotSet">
|
||||||
<a *ngIf="appRemoteVersion.update_available" class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases"
|
<a *ngIf="appRemoteVersion.update_available" class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://github.com/paperless-ngx/paperless-ngx/releases"
|
||||||
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
|
[ngbPopover]="updateAvailablePopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
|
||||||
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||||
@ -224,8 +235,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #updateCheckNotSet>
|
<ng-template #updateCheckNotSet>
|
||||||
<a class="small text-decoration-none" target="_blank" rel="noopener noreferrer" href="https://paperless-ngx.readthedocs.io/en/latest/configuration.html#update-checking"
|
<a class="small text-decoration-none" routerLink="/settings" fragment="update-checking"
|
||||||
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter:mouseleave" container="body">
|
[ngbPopover]="updateCheckingNotEnabledPopContent" popoverClass="shadow" triggers="mouseenter" container="body">
|
||||||
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
<svg fill="currentColor" class="me-1" width="1.2em" height="1.2em" style="vertical-align: text-top;" viewBox="0 0 16 16">
|
||||||
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
<use xlink:href="assets/bootstrap-icons.svg#info-circle" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, HostListener } from '@angular/core'
|
import { Component, HostListener, OnInit } from '@angular/core'
|
||||||
import { FormControl } from '@angular/forms'
|
import { FormControl } from '@angular/forms'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { from, Observable } from 'rxjs'
|
import { from, Observable } from 'rxjs'
|
||||||
@ -32,7 +32,7 @@ import { ToastService } from 'src/app/services/toast.service'
|
|||||||
templateUrl: './app-frame.component.html',
|
templateUrl: './app-frame.component.html',
|
||||||
styleUrls: ['./app-frame.component.scss'],
|
styleUrls: ['./app-frame.component.scss'],
|
||||||
})
|
})
|
||||||
export class AppFrameComponent implements ComponentCanDeactivate {
|
export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
|
||||||
constructor(
|
constructor(
|
||||||
public router: Router,
|
public router: Router,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
@ -43,14 +43,14 @@ export class AppFrameComponent implements ComponentCanDeactivate {
|
|||||||
private list: DocumentListViewService,
|
private list: DocumentListViewService,
|
||||||
public settingsService: SettingsService,
|
public settingsService: SettingsService,
|
||||||
public tasksService: TasksService,
|
public tasksService: TasksService,
|
||||||
private toastService: ToastService
|
private readonly toastService: ToastService
|
||||||
) {
|
) {}
|
||||||
this.remoteVersionService
|
|
||||||
.checkForUpdates()
|
ngOnInit(): void {
|
||||||
.subscribe((appRemoteVersion: AppRemoteVersion) => {
|
if (this.settingsService.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)) {
|
||||||
this.appRemoteVersion = appRemoteVersion
|
this.checkForUpdates()
|
||||||
})
|
}
|
||||||
tasksService.reload()
|
this.tasksService.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
versionString = `${environment.appTitle} ${environment.version}`
|
versionString = `${environment.appTitle} ${environment.version}`
|
||||||
@ -182,4 +182,30 @@ export class AppFrameComponent implements ComponentCanDeactivate {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkForUpdates() {
|
||||||
|
this.remoteVersionService
|
||||||
|
.checkForUpdates()
|
||||||
|
.subscribe((appRemoteVersion: AppRemoteVersion) => {
|
||||||
|
this.appRemoteVersion = appRemoteVersion
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpdateChecking(enable: boolean) {
|
||||||
|
this.settingsService.set(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED, enable)
|
||||||
|
this.settingsService
|
||||||
|
.storeSettings()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe({
|
||||||
|
error: (error) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`An error occurred while saving update checking settings.`
|
||||||
|
)
|
||||||
|
console.log(error)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (enable) {
|
||||||
|
this.checkForUpdates()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Component } from '@angular/core'
|
|||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||||
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
|
||||||
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
@ -31,7 +32,7 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl
|
|||||||
getForm(): FormGroup {
|
getForm(): FormGroup {
|
||||||
return new FormGroup({
|
return new FormGroup({
|
||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
matching_algorithm: new FormControl(1),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ import { Component } from '@angular/core'
|
|||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||||
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
|
||||||
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
@ -31,7 +32,7 @@ export class DocumentTypeEditDialogComponent extends EditDialogComponent<Paperle
|
|||||||
getForm(): FormGroup {
|
getForm(): FormGroup {
|
||||||
return new FormGroup({
|
return new FormGroup({
|
||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
matching_algorithm: new FormControl(1),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ import { Component } from '@angular/core'
|
|||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||||
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
|
||||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
@ -42,7 +43,7 @@ export class StoragePathEditDialogComponent extends EditDialogComponent<Paperles
|
|||||||
return new FormGroup({
|
return new FormGroup({
|
||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
path: new FormControl(''),
|
path: new FormControl(''),
|
||||||
matching_algorithm: new FormControl(1),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,7 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
|
|||||||
import { TagService } from 'src/app/services/rest/tag.service'
|
import { TagService } from 'src/app/services/rest/tag.service'
|
||||||
import { ToastService } from 'src/app/services/toast.service'
|
import { ToastService } from 'src/app/services/toast.service'
|
||||||
import { randomColor } from 'src/app/utils/color'
|
import { randomColor } from 'src/app/utils/color'
|
||||||
|
import { DEFAULT_MATCHING_ALGORITHM } from 'src/app/data/matching-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tag-edit-dialog',
|
selector: 'app-tag-edit-dialog',
|
||||||
@ -34,7 +35,7 @@ export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
|
|||||||
name: new FormControl(''),
|
name: new FormControl(''),
|
||||||
color: new FormControl(randomColor()),
|
color: new FormControl(randomColor()),
|
||||||
is_inbox_tag: new FormControl(false),
|
is_inbox_tag: new FormControl(false),
|
||||||
matching_algorithm: new FormControl(1),
|
matching_algorithm: new FormControl(DEFAULT_MATCHING_ALGORITHM),
|
||||||
match: new FormControl(''),
|
match: new FormControl(''),
|
||||||
is_insensitive: new FormControl(true),
|
is_insensitive: new FormControl(true),
|
||||||
})
|
})
|
||||||
|
@ -17,25 +17,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group-xs {
|
|
||||||
> .btn {
|
|
||||||
padding: 0.2rem 0.25rem;
|
|
||||||
font-size: 0.675rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
border-radius: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .btn:not(:first-child) {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .btn:not(:last-child) {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-group > label.disabled {
|
.btn-group > label.disabled {
|
||||||
filter: brightness(0.5);
|
filter: brightness(0.5);
|
||||||
|
|
||||||
|
@ -64,9 +64,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-100 d-xxl-none"></div>
|
<div class="w-100 d-xxl-none"></div>
|
||||||
<div class="col col-xl-auto ps-0">
|
<div class="col col-xl-auto ps-xxl-0">
|
||||||
<button class="btn btn-link btn-sm px-0" [disabled]="!rulesModified" (click)="resetSelected()">
|
<button class="btn btn-link btn-sm px-0" [disabled]="!rulesModified" (click)="resetSelected()">
|
||||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1 ms-n1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||||
</svg><ng-container i18n>Reset filters</ng-container>
|
</svg><ng-container i18n>Reset filters</ng-container>
|
||||||
</button>
|
</button>
|
||||||
|
@ -127,6 +127,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 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>
|
||||||
|
<app-input-check i18n-title title="Enable update checking" formControlName="updateCheckingEnabled" i18n-hint hint="Note that for users of thirdy-party containers e.g. linuxserver.io this notification may be 'ahead' of the current third-party release."></app-input-check>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="mt-4" i18n>Bulk editing</h4>
|
<h4 class="mt-4" i18n>Bulk editing</h4>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
@ -205,5 +220,5 @@
|
|||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" [disabled]="!(isDirty$ | async)" i18n>Save</button>
|
<button type="submit" class="btn btn-primary mb-2" [disabled]="!(isDirty$ | async)" i18n>Save</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { Component, Inject, LOCALE_ID, OnInit, OnDestroy } from '@angular/core'
|
import {
|
||||||
|
Component,
|
||||||
|
Inject,
|
||||||
|
LOCALE_ID,
|
||||||
|
OnInit,
|
||||||
|
OnDestroy,
|
||||||
|
AfterViewInit,
|
||||||
|
} from '@angular/core'
|
||||||
import { FormControl, FormGroup } from '@angular/forms'
|
import { FormControl, FormGroup } from '@angular/forms'
|
||||||
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
|
||||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||||
@ -9,8 +16,18 @@ import {
|
|||||||
} from 'src/app/services/settings.service'
|
} from 'src/app/services/settings.service'
|
||||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
||||||
import { Observable, Subscription, BehaviorSubject, first } from 'rxjs'
|
import {
|
||||||
|
Observable,
|
||||||
|
Subscription,
|
||||||
|
BehaviorSubject,
|
||||||
|
first,
|
||||||
|
tap,
|
||||||
|
takeUntil,
|
||||||
|
Subject,
|
||||||
|
} from 'rxjs'
|
||||||
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
|
||||||
|
import { ActivatedRoute } from '@angular/router'
|
||||||
|
import { ViewportScroller } from '@angular/common'
|
||||||
import { ForwardRefHandling } from '@angular/compiler'
|
import { ForwardRefHandling } from '@angular/compiler'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -18,7 +35,9 @@ import { ForwardRefHandling } from '@angular/compiler'
|
|||||||
templateUrl: './settings.component.html',
|
templateUrl: './settings.component.html',
|
||||||
styleUrls: ['./settings.component.scss'],
|
styleUrls: ['./settings.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
export class SettingsComponent
|
||||||
|
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
|
||||||
|
{
|
||||||
savedViewGroup = new FormGroup({})
|
savedViewGroup = new FormGroup({})
|
||||||
|
|
||||||
settingsForm = new FormGroup({
|
settingsForm = new FormGroup({
|
||||||
@ -40,6 +59,7 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
notificationsConsumerFailed: new FormControl(null),
|
notificationsConsumerFailed: new FormControl(null),
|
||||||
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
notificationsConsumerSuppressOnDashboard: new FormControl(null),
|
||||||
commentsEnabled: new FormControl(null),
|
commentsEnabled: new FormControl(null),
|
||||||
|
updateCheckingEnabled: new FormControl(null),
|
||||||
})
|
})
|
||||||
|
|
||||||
savedViews: PaperlessSavedView[]
|
savedViews: PaperlessSavedView[]
|
||||||
@ -47,7 +67,9 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
store: BehaviorSubject<any>
|
store: BehaviorSubject<any>
|
||||||
storeSub: Subscription
|
storeSub: Subscription
|
||||||
isDirty$: Observable<boolean>
|
isDirty$: Observable<boolean>
|
||||||
isDirty: Boolean = false
|
isDirty: boolean = false
|
||||||
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
savePending: boolean = false
|
||||||
|
|
||||||
get computedDateLocale(): string {
|
get computedDateLocale(): string {
|
||||||
return (
|
return (
|
||||||
@ -57,29 +79,28 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get displayLanguageIsDirty(): boolean {
|
|
||||||
return (
|
|
||||||
this.settingsForm.get('displayLanguage').value !=
|
|
||||||
this.store?.getValue()['displayLanguage']
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public savedViewService: SavedViewService,
|
public savedViewService: SavedViewService,
|
||||||
private documentListViewService: DocumentListViewService,
|
private documentListViewService: DocumentListViewService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private settings: SettingsService,
|
private settings: SettingsService,
|
||||||
@Inject(LOCALE_ID) public currentLocale: string
|
@Inject(LOCALE_ID) public currentLocale: string,
|
||||||
|
private viewportScroller: ViewportScroller,
|
||||||
|
private activatedRoute: ActivatedRoute
|
||||||
) {
|
) {
|
||||||
this.settings.changed.subscribe({
|
this.settings.settingsSaved.subscribe(() => {
|
||||||
next: () => {
|
if (!this.savePending) this.initialize()
|
||||||
this.settingsForm.patchValue(this.getCurrentSettings(), {
|
|
||||||
emitEvent: false,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (this.activatedRoute.snapshot.fragment) {
|
||||||
|
this.viewportScroller.scrollToAnchor(
|
||||||
|
this.activatedRoute.snapshot.fragment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getCurrentSettings() {
|
private getCurrentSettings() {
|
||||||
return {
|
return {
|
||||||
bulkEditConfirmationDialogs: this.settings.get(
|
bulkEditConfirmationDialogs: this.settings.get(
|
||||||
@ -91,7 +112,6 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
documentListItemPerPage: this.settings.get(
|
documentListItemPerPage: this.settings.get(
|
||||||
SETTINGS_KEYS.DOCUMENT_LIST_SIZE
|
SETTINGS_KEYS.DOCUMENT_LIST_SIZE
|
||||||
),
|
),
|
||||||
slimSidebarEnabled: this.settings.get(SETTINGS_KEYS.SLIM_SIDEBAR),
|
|
||||||
darkModeUseSystem: this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM),
|
darkModeUseSystem: this.settings.get(SETTINGS_KEYS.DARK_MODE_USE_SYSTEM),
|
||||||
darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED),
|
darkModeEnabled: this.settings.get(SETTINGS_KEYS.DARK_MODE_ENABLED),
|
||||||
darkModeInvertThumbs: this.settings.get(
|
darkModeInvertThumbs: this.settings.get(
|
||||||
@ -118,55 +138,68 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD
|
SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUPPRESS_ON_DASHBOARD
|
||||||
),
|
),
|
||||||
commentsEnabled: this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED),
|
commentsEnabled: this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED),
|
||||||
|
updateCheckingEnabled: this.settings.get(
|
||||||
|
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.savedViewService.listAll().subscribe((r) => {
|
this.savedViewService.listAll().subscribe((r) => {
|
||||||
this.savedViews = r.results
|
this.savedViews = r.results
|
||||||
let storeData = this.getCurrentSettings()
|
this.initialize()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for (let view of this.savedViews) {
|
initialize() {
|
||||||
storeData.savedViews[view.id.toString()] = {
|
this.unsubscribeNotifier.next(true)
|
||||||
id: view.id,
|
|
||||||
name: view.name,
|
let storeData = this.getCurrentSettings()
|
||||||
show_on_dashboard: view.show_on_dashboard,
|
|
||||||
show_in_sidebar: view.show_in_sidebar,
|
for (let view of this.savedViews) {
|
||||||
}
|
storeData.savedViews[view.id.toString()] = {
|
||||||
this.savedViewGroup.addControl(
|
id: view.id,
|
||||||
view.id.toString(),
|
name: view.name,
|
||||||
new FormGroup({
|
show_on_dashboard: view.show_on_dashboard,
|
||||||
id: new FormControl(null),
|
show_in_sidebar: view.show_in_sidebar,
|
||||||
name: new FormControl(null),
|
|
||||||
show_on_dashboard: new FormControl(null),
|
|
||||||
show_in_sidebar: new FormControl(null),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
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),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this.store = new BehaviorSubject(storeData)
|
this.store = new BehaviorSubject(storeData)
|
||||||
|
|
||||||
this.storeSub = this.store.asObservable().subscribe((state) => {
|
this.storeSub = this.store.asObservable().subscribe((state) => {
|
||||||
this.settingsForm.patchValue(state, { emitEvent: false })
|
this.settingsForm.patchValue(state, { emitEvent: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize dirtyCheck
|
// Initialize dirtyCheck
|
||||||
this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable())
|
this.isDirty$ = dirtyCheck(this.settingsForm, this.store.asObservable())
|
||||||
|
|
||||||
// Record dirty in case we need to 'undo' appearance settings if not saved on close
|
// Record dirty in case we need to 'undo' appearance settings if not saved on close
|
||||||
this.isDirty$.subscribe((dirty) => {
|
this.isDirty$
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe((dirty) => {
|
||||||
this.isDirty = dirty
|
this.isDirty = dirty
|
||||||
})
|
})
|
||||||
|
|
||||||
// "Live" visual changes prior to save
|
// "Live" visual changes prior to save
|
||||||
this.settingsForm.valueChanges.subscribe(() => {
|
this.settingsForm.valueChanges
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(() => {
|
||||||
this.settings.updateAppearanceSettings(
|
this.settings.updateAppearanceSettings(
|
||||||
this.settingsForm.get('darkModeUseSystem').value,
|
this.settingsForm.get('darkModeUseSystem').value,
|
||||||
this.settingsForm.get('darkModeEnabled').value,
|
this.settingsForm.get('darkModeEnabled').value,
|
||||||
this.settingsForm.get('themeColor').value
|
this.settingsForm.get('themeColor').value
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@ -185,7 +218,14 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private saveLocalSettings() {
|
private saveLocalSettings() {
|
||||||
const reloadRequired = this.displayLanguageIsDirty // just this one, for now
|
this.savePending = true
|
||||||
|
const reloadRequired =
|
||||||
|
this.settingsForm.value.displayLanguage !=
|
||||||
|
this.store?.getValue()['displayLanguage'] || // displayLanguage is dirty
|
||||||
|
(this.settingsForm.value.updateCheckingEnabled !=
|
||||||
|
this.store?.getValue()['updateCheckingEnabled'] &&
|
||||||
|
this.settingsForm.value.updateCheckingEnabled) // update checking was turned on
|
||||||
|
|
||||||
this.settings.set(
|
this.settings.set(
|
||||||
SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
|
SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE,
|
||||||
this.settingsForm.value.bulkEditApplyOnClose
|
this.settingsForm.value.bulkEditApplyOnClose
|
||||||
@ -250,10 +290,15 @@ export class SettingsComponent implements OnInit, OnDestroy, DirtyComponent {
|
|||||||
SETTINGS_KEYS.COMMENTS_ENABLED,
|
SETTINGS_KEYS.COMMENTS_ENABLED,
|
||||||
this.settingsForm.value.commentsEnabled
|
this.settingsForm.value.commentsEnabled
|
||||||
)
|
)
|
||||||
|
this.settings.set(
|
||||||
|
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED,
|
||||||
|
this.settingsForm.value.updateCheckingEnabled
|
||||||
|
)
|
||||||
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
|
this.settings.setLanguage(this.settingsForm.value.displayLanguage)
|
||||||
this.settings
|
this.settings
|
||||||
.storeSettings()
|
.storeSettings()
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
|
.pipe(tap(() => (this.savePending = false)))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.store.next(this.settingsForm.value)
|
this.store.next(this.settingsForm.value)
|
||||||
|
@ -6,6 +6,7 @@ export const MATCH_LITERAL = 3
|
|||||||
export const MATCH_REGEX = 4
|
export const MATCH_REGEX = 4
|
||||||
export const MATCH_FUZZY = 5
|
export const MATCH_FUZZY = 5
|
||||||
export const MATCH_AUTO = 6
|
export const MATCH_AUTO = 6
|
||||||
|
export const DEFAULT_MATCHING_ALGORITHM = MATCH_AUTO
|
||||||
|
|
||||||
export const MATCHING_ALGORITHMS = [
|
export const MATCHING_ALGORITHMS = [
|
||||||
{
|
{
|
||||||
|
@ -29,8 +29,6 @@ export interface PaperlessDocument extends ObjectWithId {
|
|||||||
|
|
||||||
content?: string
|
content?: string
|
||||||
|
|
||||||
file_type?: string
|
|
||||||
|
|
||||||
tags$?: Observable<PaperlessTag[]>
|
tags$?: Observable<PaperlessTag[]>
|
||||||
|
|
||||||
tags?: number[]
|
tags?: number[]
|
||||||
@ -47,7 +45,7 @@ export interface PaperlessDocument extends ObjectWithId {
|
|||||||
|
|
||||||
added?: Date
|
added?: Date
|
||||||
|
|
||||||
file_name?: string
|
original_file_name?: string
|
||||||
|
|
||||||
download_url?: string
|
download_url?: string
|
||||||
|
|
||||||
|
@ -38,6 +38,9 @@ export const SETTINGS_KEYS = {
|
|||||||
'general-settings:notifications:consumer-suppress-on-dashboard',
|
'general-settings:notifications:consumer-suppress-on-dashboard',
|
||||||
COMMENTS_ENABLED: 'general-settings:comments-enabled',
|
COMMENTS_ENABLED: 'general-settings:comments-enabled',
|
||||||
SLIM_SIDEBAR: 'general-settings:slim-sidebar',
|
SLIM_SIDEBAR: 'general-settings:slim-sidebar',
|
||||||
|
UPDATE_CHECKING_ENABLED: 'general-settings:update-checking:enabled',
|
||||||
|
UPDATE_CHECKING_BACKEND_SETTING:
|
||||||
|
'general-settings:update-checking:backend-setting',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SETTINGS: PaperlessUiSetting[] = [
|
export const SETTINGS: PaperlessUiSetting[] = [
|
||||||
@ -126,4 +129,14 @@ export const SETTINGS: PaperlessUiSetting[] = [
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: SETTINGS_KEYS.UPDATE_CHECKING_ENABLED,
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING,
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
@ -6,7 +6,6 @@ import { environment } from 'src/environments/environment'
|
|||||||
export interface AppRemoteVersion {
|
export interface AppRemoteVersion {
|
||||||
version: string
|
version: string
|
||||||
update_available: boolean
|
update_available: boolean
|
||||||
feature_is_set: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
@ -47,7 +47,7 @@ export class SettingsService {
|
|||||||
|
|
||||||
public displayName: string
|
public displayName: string
|
||||||
|
|
||||||
public changed = new EventEmitter()
|
public settingsSaved: EventEmitter<any> = new EventEmitter()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
rendererFactory: RendererFactory2,
|
rendererFactory: RendererFactory2,
|
||||||
@ -316,13 +316,7 @@ export class SettingsService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string): any {
|
private getSettingRawValue(key: string): any {
|
||||||
let setting = SETTINGS.find((s) => s.key == key)
|
|
||||||
|
|
||||||
if (!setting) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = null
|
let value = null
|
||||||
// parse key:key:key into nested object
|
// parse key:key:key into nested object
|
||||||
const keys = key.replace('general-settings:', '').split(':')
|
const keys = key.replace('general-settings:', '').split(':')
|
||||||
@ -333,6 +327,17 @@ export class SettingsService {
|
|||||||
if (index == keys.length - 1) value = settingObj[keyPart]
|
if (index == keys.length - 1) value = settingObj[keyPart]
|
||||||
else settingObj = settingObj[keyPart]
|
else settingObj = settingObj[keyPart]
|
||||||
})
|
})
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: string): any {
|
||||||
|
let setting = SETTINGS.find((s) => s.key == key)
|
||||||
|
|
||||||
|
if (!setting) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = this.getSettingRawValue(key)
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
switch (setting.type) {
|
switch (setting.type) {
|
||||||
@ -362,10 +367,19 @@ export class SettingsService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private settingIsSet(key: string): boolean {
|
||||||
|
let value = this.getSettingRawValue(key)
|
||||||
|
return value != null
|
||||||
|
}
|
||||||
|
|
||||||
storeSettings(): Observable<any> {
|
storeSettings(): Observable<any> {
|
||||||
return this.http
|
return this.http.post(this.baseUrl, { settings: this.settings }).pipe(
|
||||||
.post(this.baseUrl, { settings: this.settings })
|
tap((results) => {
|
||||||
.pipe(tap((result) => this.changed.emit(!!result.success)))
|
if (results.success) {
|
||||||
|
this.settingsSaved.emit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
maybeMigrateSettings() {
|
maybeMigrateSettings() {
|
||||||
@ -405,5 +419,31 @@ export class SettingsService {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED) &&
|
||||||
|
this.get(SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING) != 'default'
|
||||||
|
) {
|
||||||
|
this.set(
|
||||||
|
SETTINGS_KEYS.UPDATE_CHECKING_ENABLED,
|
||||||
|
this.get(SETTINGS_KEYS.UPDATE_CHECKING_BACKEND_SETTING).toString() ===
|
||||||
|
'true'
|
||||||
|
)
|
||||||
|
|
||||||
|
this.storeSettings()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe({
|
||||||
|
error: (e) => {
|
||||||
|
this.toastService.showError(
|
||||||
|
'Error migrating update checking setting'
|
||||||
|
)
|
||||||
|
console.log(e)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get updateCheckingIsSet(): boolean {
|
||||||
|
return this.settingIsSet(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ export const environment = {
|
|||||||
apiBaseUrl: document.baseURI + 'api/',
|
apiBaseUrl: document.baseURI + 'api/',
|
||||||
apiVersion: '2',
|
apiVersion: '2',
|
||||||
appTitle: 'Paperless-ngx',
|
appTitle: 'Paperless-ngx',
|
||||||
version: '1.9.0-dev',
|
version: '1.9.2-dev',
|
||||||
webSocketHost: window.location.host,
|
webSocketHost: window.location.host,
|
||||||
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
|
||||||
webSocketBaseUrl: base_url.pathname + 'ws/',
|
webSocketBaseUrl: base_url.pathname + 'ws/',
|
||||||
|
@ -540,6 +540,25 @@ a.badge {
|
|||||||
border-color: var(--bs-primary);
|
border-color: var(--bs-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-group-xs {
|
||||||
|
> .btn {
|
||||||
|
padding: 0.2rem 0.25rem;
|
||||||
|
font-size: 0.675rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
border-radius: 0.15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .btn:not(:first-child) {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .btn:not(:last-child) {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
color: var(--pngx-body-color-accent)
|
color: var(--pngx-body-color-accent)
|
||||||
}
|
}
|
||||||
|
@ -111,14 +111,16 @@ class Consumer(LoggingMixin):
|
|||||||
def pre_check_duplicate(self):
|
def pre_check_duplicate(self):
|
||||||
with open(self.path, "rb") as f:
|
with open(self.path, "rb") as f:
|
||||||
checksum = hashlib.md5(f.read()).hexdigest()
|
checksum = hashlib.md5(f.read()).hexdigest()
|
||||||
if Document.objects.filter(
|
existing_doc = Document.objects.filter(
|
||||||
Q(checksum=checksum) | Q(archive_checksum=checksum),
|
Q(checksum=checksum) | Q(archive_checksum=checksum),
|
||||||
).exists():
|
)
|
||||||
|
if existing_doc.exists():
|
||||||
if settings.CONSUMER_DELETE_DUPLICATES:
|
if settings.CONSUMER_DELETE_DUPLICATES:
|
||||||
os.unlink(self.path)
|
os.unlink(self.path)
|
||||||
self._fail(
|
self._fail(
|
||||||
MESSAGE_DOCUMENT_ALREADY_EXISTS,
|
MESSAGE_DOCUMENT_ALREADY_EXISTS,
|
||||||
f"Not consuming {self.filename}: It is a duplicate.",
|
f"Not consuming {self.filename}: It is a duplicate of"
|
||||||
|
f" {existing_doc.get().title} (#{existing_doc.get().pk})",
|
||||||
)
|
)
|
||||||
|
|
||||||
def pre_check_directories(self):
|
def pre_check_directories(self):
|
||||||
|
@ -608,6 +608,15 @@ class UiSettingsViewSerializer(serializers.ModelSerializer):
|
|||||||
"settings",
|
"settings",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def validate_settings(self, settings):
|
||||||
|
# we never save update checking backend setting
|
||||||
|
if "update_checking" in settings:
|
||||||
|
try:
|
||||||
|
settings["update_checking"].pop("backend_setting")
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return settings
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
ui_settings = UiSettings.objects.update_or_create(
|
ui_settings = UiSettings.objects.update_or_create(
|
||||||
user=validated_data.get("user"),
|
user=validated_data.get("user"),
|
||||||
|
@ -112,10 +112,22 @@ def consume_file(
|
|||||||
newname = f"{str(n)}_" + override_filename
|
newname = f"{str(n)}_" + override_filename
|
||||||
else:
|
else:
|
||||||
newname = None
|
newname = None
|
||||||
|
|
||||||
|
# If the file is an upload, it's in the scratch directory
|
||||||
|
# Move it to consume directory to be picked up
|
||||||
|
# Otherwise, use the current parent to keep possible tags
|
||||||
|
# from subdirectories
|
||||||
|
try:
|
||||||
|
# is_relative_to would be nicer, but new in 3.9
|
||||||
|
_ = path.relative_to(settings.SCRATCH_DIR)
|
||||||
|
save_to_dir = settings.CONSUMPTION_DIR
|
||||||
|
except ValueError:
|
||||||
|
save_to_dir = path.parent
|
||||||
|
|
||||||
barcodes.save_to_dir(
|
barcodes.save_to_dir(
|
||||||
document,
|
document,
|
||||||
newname=newname,
|
newname=newname,
|
||||||
target_dir=path.parent,
|
target_dir=save_to_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Delete the PDF file which was split
|
# Delete the PDF file which was split
|
||||||
|
@ -1581,7 +1581,11 @@ class TestApiUiSettings(DirectoriesMixin, APITestCase):
|
|||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
response.data["settings"],
|
response.data["settings"],
|
||||||
{},
|
{
|
||||||
|
"update_checking": {
|
||||||
|
"backend_setting": "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_api_set_ui_settings(self):
|
def test_api_set_ui_settings(self):
|
||||||
@ -2542,38 +2546,6 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
def test_remote_version_default(self):
|
|
||||||
response = self.client.get(self.ENDPOINT)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertDictEqual(
|
|
||||||
response.data,
|
|
||||||
{
|
|
||||||
"version": "0.0.0",
|
|
||||||
"update_available": False,
|
|
||||||
"feature_is_set": False,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
ENABLE_UPDATE_CHECK=False,
|
|
||||||
)
|
|
||||||
def test_remote_version_disabled(self):
|
|
||||||
response = self.client.get(self.ENDPOINT)
|
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertDictEqual(
|
|
||||||
response.data,
|
|
||||||
{
|
|
||||||
"version": "0.0.0",
|
|
||||||
"update_available": False,
|
|
||||||
"feature_is_set": True,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
ENABLE_UPDATE_CHECK=True,
|
|
||||||
)
|
|
||||||
@mock.patch("urllib.request.urlopen")
|
@mock.patch("urllib.request.urlopen")
|
||||||
def test_remote_version_enabled_no_update_prefix(self, urlopen_mock):
|
def test_remote_version_enabled_no_update_prefix(self, urlopen_mock):
|
||||||
|
|
||||||
@ -2591,13 +2563,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
|||||||
{
|
{
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"update_available": False,
|
"update_available": False,
|
||||||
"feature_is_set": True,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
ENABLE_UPDATE_CHECK=True,
|
|
||||||
)
|
|
||||||
@mock.patch("urllib.request.urlopen")
|
@mock.patch("urllib.request.urlopen")
|
||||||
def test_remote_version_enabled_no_update_no_prefix(self, urlopen_mock):
|
def test_remote_version_enabled_no_update_no_prefix(self, urlopen_mock):
|
||||||
|
|
||||||
@ -2617,13 +2585,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
|||||||
{
|
{
|
||||||
"version": version.__full_version_str__,
|
"version": version.__full_version_str__,
|
||||||
"update_available": False,
|
"update_available": False,
|
||||||
"feature_is_set": True,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
ENABLE_UPDATE_CHECK=True,
|
|
||||||
)
|
|
||||||
@mock.patch("urllib.request.urlopen")
|
@mock.patch("urllib.request.urlopen")
|
||||||
def test_remote_version_enabled_update(self, urlopen_mock):
|
def test_remote_version_enabled_update(self, urlopen_mock):
|
||||||
|
|
||||||
@ -2650,13 +2614,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
|||||||
{
|
{
|
||||||
"version": new_version_str,
|
"version": new_version_str,
|
||||||
"update_available": True,
|
"update_available": True,
|
||||||
"feature_is_set": True,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
ENABLE_UPDATE_CHECK=True,
|
|
||||||
)
|
|
||||||
@mock.patch("urllib.request.urlopen")
|
@mock.patch("urllib.request.urlopen")
|
||||||
def test_remote_version_bad_json(self, urlopen_mock):
|
def test_remote_version_bad_json(self, urlopen_mock):
|
||||||
|
|
||||||
@ -2674,13 +2634,9 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
|||||||
{
|
{
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"update_available": False,
|
"update_available": False,
|
||||||
"feature_is_set": True,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@override_settings(
|
|
||||||
ENABLE_UPDATE_CHECK=True,
|
|
||||||
)
|
|
||||||
@mock.patch("urllib.request.urlopen")
|
@mock.patch("urllib.request.urlopen")
|
||||||
def test_remote_version_exception(self, urlopen_mock):
|
def test_remote_version_exception(self, urlopen_mock):
|
||||||
|
|
||||||
@ -2698,7 +2654,6 @@ class TestApiRemoteVersion(DirectoriesMixin, APITestCase):
|
|||||||
{
|
{
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"update_available": False,
|
"update_available": False,
|
||||||
"feature_is_set": True,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -261,6 +261,9 @@ class DocumentViewSet(
|
|||||||
file_handle = doc.source_file
|
file_handle = doc.source_file
|
||||||
filename = doc.get_public_filename()
|
filename = doc.get_public_filename()
|
||||||
mime_type = doc.mime_type
|
mime_type = doc.mime_type
|
||||||
|
# Support browser previewing csv files by using text mime type
|
||||||
|
if mime_type in {"application/csv", "text/csv"} and disposition == "inline":
|
||||||
|
mime_type = "text/plain"
|
||||||
|
|
||||||
if doc.storage_type == Document.STORAGE_TYPE_GPG:
|
if doc.storage_type == Document.STORAGE_TYPE_GPG:
|
||||||
file_handle = GnuPG.decrypted(file_handle)
|
file_handle = GnuPG.decrypted(file_handle)
|
||||||
@ -780,42 +783,38 @@ class RemoteVersionView(GenericAPIView):
|
|||||||
remote_version = "0.0.0"
|
remote_version = "0.0.0"
|
||||||
is_greater_than_current = False
|
is_greater_than_current = False
|
||||||
current_version = packaging_version.parse(version.__full_version_str__)
|
current_version = packaging_version.parse(version.__full_version_str__)
|
||||||
# TODO: this can likely be removed when frontend settings are saved to DB
|
try:
|
||||||
feature_is_set = settings.ENABLE_UPDATE_CHECK != "default"
|
req = urllib.request.Request(
|
||||||
if feature_is_set and settings.ENABLE_UPDATE_CHECK:
|
"https://api.github.com/repos/paperless-ngx/"
|
||||||
try:
|
"paperless-ngx/releases/latest",
|
||||||
req = urllib.request.Request(
|
|
||||||
"https://api.github.com/repos/paperless-ngx/"
|
|
||||||
"paperless-ngx/releases/latest",
|
|
||||||
)
|
|
||||||
# Ensure a JSON response
|
|
||||||
req.add_header("Accept", "application/json")
|
|
||||||
|
|
||||||
with urllib.request.urlopen(req) as response:
|
|
||||||
remote = response.read().decode("utf-8")
|
|
||||||
try:
|
|
||||||
remote_json = json.loads(remote)
|
|
||||||
remote_version = remote_json["tag_name"]
|
|
||||||
# Basically PEP 616 but that only went in 3.9
|
|
||||||
if remote_version.startswith("ngx-"):
|
|
||||||
remote_version = remote_version[len("ngx-") :]
|
|
||||||
except ValueError:
|
|
||||||
logger.debug("An error occurred parsing remote version json")
|
|
||||||
except urllib.error.URLError:
|
|
||||||
logger.debug("An error occurred checking for available updates")
|
|
||||||
|
|
||||||
is_greater_than_current = (
|
|
||||||
packaging_version.parse(
|
|
||||||
remote_version,
|
|
||||||
)
|
|
||||||
> current_version
|
|
||||||
)
|
)
|
||||||
|
# Ensure a JSON response
|
||||||
|
req.add_header("Accept", "application/json")
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req) as response:
|
||||||
|
remote = response.read().decode("utf-8")
|
||||||
|
try:
|
||||||
|
remote_json = json.loads(remote)
|
||||||
|
remote_version = remote_json["tag_name"]
|
||||||
|
# Basically PEP 616 but that only went in 3.9
|
||||||
|
if remote_version.startswith("ngx-"):
|
||||||
|
remote_version = remote_version[len("ngx-") :]
|
||||||
|
except ValueError:
|
||||||
|
logger.debug("An error occurred parsing remote version json")
|
||||||
|
except urllib.error.URLError:
|
||||||
|
logger.debug("An error occurred checking for available updates")
|
||||||
|
|
||||||
|
is_greater_than_current = (
|
||||||
|
packaging_version.parse(
|
||||||
|
remote_version,
|
||||||
|
)
|
||||||
|
> current_version
|
||||||
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"version": remote_version,
|
"version": remote_version,
|
||||||
"update_available": is_greater_than_current,
|
"update_available": is_greater_than_current,
|
||||||
"feature_is_set": feature_is_set,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -848,15 +847,23 @@ class UiSettingsView(GenericAPIView):
|
|||||||
displayname = user.username
|
displayname = user.username
|
||||||
if user.first_name or user.last_name:
|
if user.first_name or user.last_name:
|
||||||
displayname = " ".join([user.first_name, user.last_name])
|
displayname = " ".join([user.first_name, user.last_name])
|
||||||
settings = {}
|
ui_settings = {}
|
||||||
if hasattr(user, "ui_settings"):
|
if hasattr(user, "ui_settings"):
|
||||||
settings = user.ui_settings.settings
|
ui_settings = user.ui_settings.settings
|
||||||
|
if "update_checking" in ui_settings:
|
||||||
|
ui_settings["update_checking"][
|
||||||
|
"backend_setting"
|
||||||
|
] = settings.ENABLE_UPDATE_CHECK
|
||||||
|
else:
|
||||||
|
ui_settings["update_checking"] = {
|
||||||
|
"backend_setting": settings.ENABLE_UPDATE_CHECK,
|
||||||
|
}
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"user_id": user.id,
|
"user_id": user.id,
|
||||||
"username": user.username,
|
"username": user.username,
|
||||||
"display_name": displayname,
|
"display_name": displayname,
|
||||||
"settings": settings,
|
"settings": ui_settings,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -127,10 +127,10 @@ def settings_values_check(app_configs, **kwargs):
|
|||||||
Error(f'OCR output type "{settings.OCR_OUTPUT_TYPE}" is not valid'),
|
Error(f'OCR output type "{settings.OCR_OUTPUT_TYPE}" is not valid'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if settings.OCR_MODE not in {"force", "skip", "redo_ocr"}:
|
if settings.OCR_MODE not in {"force", "skip", "redo", "skip_noarchive"}:
|
||||||
msgs.append(Error(f'OCR output mode "{settings.OCR_MODE}" is not valid'))
|
msgs.append(Error(f'OCR output mode "{settings.OCR_MODE}" is not valid'))
|
||||||
|
|
||||||
if settings.OCR_CLEAN not in {"clean", "clean_final"}:
|
if settings.OCR_CLEAN not in {"clean", "clean-final", "none"}:
|
||||||
msgs.append(Error(f'OCR clean mode "{settings.OCR_CLEAN}" is not valid'))
|
msgs.append(Error(f'OCR clean mode "{settings.OCR_CLEAN}" is not valid'))
|
||||||
return msgs
|
return msgs
|
||||||
|
|
||||||
|
@ -319,6 +319,7 @@ DATABASES = {
|
|||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": os.path.join(DATA_DIR, "db.sqlite3"),
|
"NAME": os.path.join(DATA_DIR, "db.sqlite3"),
|
||||||
|
"OPTIONS": {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,21 +341,18 @@ if os.getenv("PAPERLESS_DBHOST"):
|
|||||||
# Leave room for future extensibility
|
# Leave room for future extensibility
|
||||||
if os.getenv("PAPERLESS_DBENGINE") == "mariadb":
|
if os.getenv("PAPERLESS_DBENGINE") == "mariadb":
|
||||||
engine = "django.db.backends.mysql"
|
engine = "django.db.backends.mysql"
|
||||||
options = {"read_default_file": "/etc/mysql/my.cnf"}
|
options = {"read_default_file": "/etc/mysql/my.cnf", "charset": "utf8mb4"}
|
||||||
else: # Default to PostgresDB
|
else: # Default to PostgresDB
|
||||||
engine = "django.db.backends.postgresql_psycopg2"
|
engine = "django.db.backends.postgresql_psycopg2"
|
||||||
options = {"sslmode": os.getenv("PAPERLESS_DBSSLMODE", "prefer")}
|
options = {"sslmode": os.getenv("PAPERLESS_DBSSLMODE", "prefer")}
|
||||||
|
|
||||||
DATABASES["default"]["ENGINE"] = engine
|
DATABASES["default"]["ENGINE"] = engine
|
||||||
for key, value in options.items():
|
DATABASES["default"]["OPTIONS"].update(options)
|
||||||
DATABASES["default"]["OPTIONS"][key] = value
|
|
||||||
|
|
||||||
if os.getenv("PAPERLESS_DB_TIMEOUT") is not None:
|
if os.getenv("PAPERLESS_DB_TIMEOUT") is not None:
|
||||||
_new_opts = {"timeout": float(os.getenv("PAPERLESS_DB_TIMEOUT"))}
|
DATABASES["default"]["OPTIONS"].update(
|
||||||
if "OPTIONS" in DATABASES["default"]:
|
{"timeout": float(os.getenv("PAPERLESS_DB_TIMEOUT"))},
|
||||||
DATABASES["default"]["OPTIONS"].update(_new_opts)
|
)
|
||||||
else:
|
|
||||||
DATABASES["default"]["OPTIONS"] = _new_opts
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
__version__: Final[Tuple[int, int, int]] = (1, 9, 0)
|
__version__: Final[Tuple[int, int, int]] = (1, 9, 2)
|
||||||
# Version string like X.Y.Z
|
# Version string like X.Y.Z
|
||||||
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
__full_version_str__: Final[str] = ".".join(map(str, __version__))
|
||||||
# Version string like X.Y
|
# Version string like X.Y
|
||||||
|
@ -4,6 +4,7 @@ import tempfile
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
import pathvalidate
|
import pathvalidate
|
||||||
@ -30,7 +31,7 @@ class MailError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class BaseMailAction:
|
class BaseMailAction:
|
||||||
def get_criteria(self):
|
def get_criteria(self) -> Dict:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def post_consume(self, M, message_uids, parameter):
|
def post_consume(self, M, message_uids, parameter):
|
||||||
@ -78,7 +79,7 @@ class TagMailAction(BaseMailAction):
|
|||||||
M.flag(message_uids, [self.keyword], True)
|
M.flag(message_uids, [self.keyword], True)
|
||||||
|
|
||||||
|
|
||||||
def get_rule_action(rule):
|
def get_rule_action(rule) -> BaseMailAction:
|
||||||
if rule.action == MailRule.MailAction.FLAG:
|
if rule.action == MailRule.MailAction.FLAG:
|
||||||
return FlagMailAction()
|
return FlagMailAction()
|
||||||
elif rule.action == MailRule.MailAction.DELETE:
|
elif rule.action == MailRule.MailAction.DELETE:
|
||||||
@ -108,7 +109,7 @@ def make_criterias(rule):
|
|||||||
return {**criterias, **get_rule_action(rule).get_criteria()}
|
return {**criterias, **get_rule_action(rule).get_criteria()}
|
||||||
|
|
||||||
|
|
||||||
def get_mailbox(server, port, security):
|
def get_mailbox(server, port, security) -> MailBox:
|
||||||
if security == MailAccount.ImapSecurity.NONE:
|
if security == MailAccount.ImapSecurity.NONE:
|
||||||
mailbox = MailBoxUnencrypted(server, port)
|
mailbox = MailBoxUnencrypted(server, port)
|
||||||
elif security == MailAccount.ImapSecurity.STARTTLS:
|
elif security == MailAccount.ImapSecurity.STARTTLS:
|
||||||
@ -167,7 +168,7 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
"Unknown correspondent selector",
|
"Unknown correspondent selector",
|
||||||
) # pragma: nocover
|
) # pragma: nocover
|
||||||
|
|
||||||
def handle_mail_account(self, account):
|
def handle_mail_account(self, account: MailAccount):
|
||||||
|
|
||||||
self.renew_logging_group()
|
self.renew_logging_group()
|
||||||
|
|
||||||
@ -181,7 +182,14 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
account.imap_security,
|
account.imap_security,
|
||||||
) as M:
|
) as M:
|
||||||
|
|
||||||
|
supports_gmail_labels = "X-GM-EXT-1" in M.client.capabilities
|
||||||
|
supports_auth_plain = "AUTH=PLAIN" in M.client.capabilities
|
||||||
|
|
||||||
|
self.log("debug", f"GMAIL Label Support: {supports_gmail_labels}")
|
||||||
|
self.log("debug", f"AUTH=PLAIN Support: {supports_auth_plain}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
M.login(account.username, account.password)
|
M.login(account.username, account.password)
|
||||||
|
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
@ -215,7 +223,11 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
|
|
||||||
for rule in account.rules.order_by("order"):
|
for rule in account.rules.order_by("order"):
|
||||||
try:
|
try:
|
||||||
total_processed_files += self.handle_mail_rule(M, rule)
|
total_processed_files += self.handle_mail_rule(
|
||||||
|
M,
|
||||||
|
rule,
|
||||||
|
supports_gmail_labels,
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log(
|
self.log(
|
||||||
"error",
|
"error",
|
||||||
@ -233,7 +245,12 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
|
|
||||||
return total_processed_files
|
return total_processed_files
|
||||||
|
|
||||||
def handle_mail_rule(self, M: MailBox, rule):
|
def handle_mail_rule(
|
||||||
|
self,
|
||||||
|
M: MailBox,
|
||||||
|
rule: MailRule,
|
||||||
|
supports_gmail_labels: bool = False,
|
||||||
|
):
|
||||||
|
|
||||||
self.log("debug", f"Rule {rule}: Selecting folder {rule.folder}")
|
self.log("debug", f"Rule {rule}: Selecting folder {rule.folder}")
|
||||||
|
|
||||||
@ -261,11 +278,19 @@ class MailAccountHandler(LoggingMixin):
|
|||||||
) from err
|
) from err
|
||||||
|
|
||||||
criterias = make_criterias(rule)
|
criterias = make_criterias(rule)
|
||||||
criterias_imap = AND(**criterias)
|
|
||||||
|
# Deal with the Gmail label extension
|
||||||
if "gmail_label" in criterias:
|
if "gmail_label" in criterias:
|
||||||
|
|
||||||
gmail_label = criterias["gmail_label"]
|
gmail_label = criterias["gmail_label"]
|
||||||
del criterias["gmail_label"]
|
del criterias["gmail_label"]
|
||||||
criterias_imap = AND(NOT(gmail_label=gmail_label), **criterias)
|
|
||||||
|
if not supports_gmail_labels:
|
||||||
|
criterias_imap = AND(**criterias)
|
||||||
|
else:
|
||||||
|
criterias_imap = AND(NOT(gmail_label=gmail_label), **criterias)
|
||||||
|
else:
|
||||||
|
criterias_imap = AND(**criterias)
|
||||||
|
|
||||||
self.log(
|
self.log(
|
||||||
"debug",
|
"debug",
|
||||||
|
70
src/paperless_mail/tests/test_live_mail.py
Normal file
70
src/paperless_mail/tests/test_live_mail.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from django.test import TestCase
|
||||||
|
from paperless_mail.mail import MailAccountHandler
|
||||||
|
from paperless_mail.mail import MailError
|
||||||
|
from paperless_mail.models import MailAccount
|
||||||
|
from paperless_mail.models import MailRule
|
||||||
|
|
||||||
|
# Only run if the environment is setup
|
||||||
|
# And the environment is not empty (forks, I think)
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
"PAPERLESS_MAIL_TEST_HOST" not in os.environ
|
||||||
|
or not len(os.environ["PAPERLESS_MAIL_TEST_HOST"]),
|
||||||
|
reason="Live server testing not enabled",
|
||||||
|
)
|
||||||
|
class TestMailLiveServer(TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
|
||||||
|
self.mail_account_handler = MailAccountHandler()
|
||||||
|
self.account = MailAccount.objects.create(
|
||||||
|
name="test",
|
||||||
|
imap_server=os.environ["PAPERLESS_MAIL_TEST_HOST"],
|
||||||
|
username=os.environ["PAPERLESS_MAIL_TEST_USER"],
|
||||||
|
password=os.environ["PAPERLESS_MAIL_TEST_PASSWD"],
|
||||||
|
imap_port=993,
|
||||||
|
)
|
||||||
|
|
||||||
|
return super().setUp()
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.account.delete()
|
||||||
|
return super().tearDown()
|
||||||
|
|
||||||
|
def test_process_non_gmail_server_flag(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
rule1 = MailRule.objects.create(
|
||||||
|
name="testrule",
|
||||||
|
account=self.account,
|
||||||
|
action=MailRule.MailAction.FLAG,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mail_account_handler.handle_mail_account(self.account)
|
||||||
|
|
||||||
|
rule1.delete()
|
||||||
|
|
||||||
|
except MailError as e:
|
||||||
|
self.fail(f"Failure: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_process_non_gmail_server_tag(self):
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
rule2 = MailRule.objects.create(
|
||||||
|
name="testrule",
|
||||||
|
account=self.account,
|
||||||
|
action=MailRule.MailAction.TAG,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mail_account_handler.handle_mail_account(self.account)
|
||||||
|
|
||||||
|
rule2.delete()
|
||||||
|
|
||||||
|
except MailError as e:
|
||||||
|
self.fail(f"Failure: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
@ -47,15 +47,16 @@ class BogusFolderManager:
|
|||||||
|
|
||||||
|
|
||||||
class BogusClient:
|
class BogusClient:
|
||||||
|
def __init__(self, messages):
|
||||||
|
self.messages: List[MailMessage] = messages
|
||||||
|
self.capabilities: List[str] = []
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, messages):
|
|
||||||
self.messages: List[MailMessage] = messages
|
|
||||||
|
|
||||||
def authenticate(self, mechanism, authobject):
|
def authenticate(self, mechanism, authobject):
|
||||||
# authobject must be a callable object
|
# authobject must be a callable object
|
||||||
auth_bytes = authobject(None)
|
auth_bytes = authobject(None)
|
||||||
@ -80,12 +81,6 @@ class BogusMailBox(ContextManager):
|
|||||||
# Note the non-ascii characters here
|
# Note the non-ascii characters here
|
||||||
UTF_PASSWORD: str = "w57äöüw4b6huwb6nhu"
|
UTF_PASSWORD: str = "w57äöüw4b6huwb6nhu"
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.messages: List[MailMessage] = []
|
self.messages: List[MailMessage] = []
|
||||||
self.messages_spam: List[MailMessage] = []
|
self.messages_spam: List[MailMessage] = []
|
||||||
@ -93,6 +88,12 @@ class BogusMailBox(ContextManager):
|
|||||||
self.client = BogusClient(self.messages)
|
self.client = BogusClient(self.messages)
|
||||||
self._host = ""
|
self._host = ""
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
pass
|
||||||
|
|
||||||
def updateClient(self):
|
def updateClient(self):
|
||||||
self.client = BogusClient(self.messages)
|
self.client = BogusClient(self.messages)
|
||||||
|
|
||||||
@ -648,6 +649,7 @@ class TestMail(DirectoriesMixin, TestCase):
|
|||||||
|
|
||||||
def test_handle_mail_account_tag_gmail(self):
|
def test_handle_mail_account_tag_gmail(self):
|
||||||
self.bogus_mailbox._host = "imap.gmail.com"
|
self.bogus_mailbox._host = "imap.gmail.com"
|
||||||
|
self.bogus_mailbox.client.capabilities = ["X-GM-EXT-1"]
|
||||||
|
|
||||||
account = MailAccount.objects.create(
|
account = MailAccount.objects.create(
|
||||||
name="test",
|
name="test",
|
||||||
|
@ -11,5 +11,6 @@ def text_consumer_declaration(sender, **kwargs):
|
|||||||
"mime_types": {
|
"mime_types": {
|
||||||
"text/plain": ".txt",
|
"text/plain": ".txt",
|
||||||
"text/csv": ".csv",
|
"text/csv": ".csv",
|
||||||
|
"application/csv": ".csv",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
BIN
src/paperless_tika/tests/samples/sample.docx
Normal file
BIN
src/paperless_tika/tests/samples/sample.docx
Normal file
Binary file not shown.
BIN
src/paperless_tika/tests/samples/sample.odt
Normal file
BIN
src/paperless_tika/tests/samples/sample.odt
Normal file
Binary file not shown.
78
src/paperless_tika/tests/test_live_tika.py
Normal file
78
src/paperless_tika/tests/test_live_tika.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from django.test import TestCase
|
||||||
|
from paperless_tika.parsers import TikaDocumentParser
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("TIKA_LIVE" not in os.environ, reason="No tika server")
|
||||||
|
class TestTikaParserAgainstServer(TestCase):
|
||||||
|
"""
|
||||||
|
This test case tests the Tika parsing against a live tika server,
|
||||||
|
if the environment contains the correct value indicating such a server
|
||||||
|
is available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SAMPLE_DIR: Final[Path] = (Path(__file__).parent / Path("samples")).resolve()
|
||||||
|
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.parser = TikaDocumentParser(logging_group=None)
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.parser.cleanup()
|
||||||
|
|
||||||
|
def test_basic_parse_odt(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- An input ODT format document
|
||||||
|
WHEN:
|
||||||
|
- The document is parsed
|
||||||
|
THEN:
|
||||||
|
- Document content is correct
|
||||||
|
- Document date is correct
|
||||||
|
"""
|
||||||
|
test_file = self.SAMPLE_DIR / Path("sample.odt")
|
||||||
|
|
||||||
|
self.parser.parse(test_file, "application/vnd.oasis.opendocument.text")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.parser.text,
|
||||||
|
"This is an ODT test document, created September 14, 2022",
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(self.parser.archive_path)
|
||||||
|
with open(self.parser.archive_path, "rb") as f:
|
||||||
|
# PDFs begin with the bytes PDF-x.y
|
||||||
|
self.assertTrue(b"PDF-" in f.read()[:10])
|
||||||
|
|
||||||
|
# TODO: Unsure what can set the Creation-Date field in a document, enable when possible
|
||||||
|
# self.assertEqual(self.parser.date, datetime.datetime(2022, 9, 14))
|
||||||
|
|
||||||
|
def test_basic_parse_docx(self):
|
||||||
|
"""
|
||||||
|
GIVEN:
|
||||||
|
- An input DOCX format document
|
||||||
|
WHEN:
|
||||||
|
- The document is parsed
|
||||||
|
THEN:
|
||||||
|
- Document content is correct
|
||||||
|
- Document date is correct
|
||||||
|
"""
|
||||||
|
test_file = self.SAMPLE_DIR / Path("sample.docx")
|
||||||
|
|
||||||
|
self.parser.parse(
|
||||||
|
test_file,
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.parser.text,
|
||||||
|
"This is an DOCX test document, also made September 14, 2022",
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(self.parser.archive_path)
|
||||||
|
with open(self.parser.archive_path, "rb") as f:
|
||||||
|
self.assertTrue(b"PDF-" in f.read()[:10])
|
||||||
|
|
||||||
|
# self.assertEqual(self.parser.date, datetime.datetime(2022, 9, 14))
|
Loading…
x
Reference in New Issue
Block a user