mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-03 18:54:40 -05:00
Compare commits
157 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
aeb49898e5 | ||
![]() |
a2c8fcd46b | ||
![]() |
357ae92d88 | ||
![]() |
74330623b3 | ||
![]() |
3813dc2e18 | ||
![]() |
3df8be0bc7 | ||
![]() |
cc25cbc026 | ||
![]() |
e98d52830f | ||
![]() |
4903e4290d | ||
![]() |
e1ba1a1898 | ||
![]() |
b29c1e91d1 | ||
![]() |
a03c7701a5 | ||
![]() |
b8c9d1316c | ||
![]() |
a63ef26d38 | ||
![]() |
96b2884458 | ||
![]() |
8543202723 | ||
![]() |
a63904b7af | ||
![]() |
eb27bc9e7d | ||
![]() |
489b24ad65 | ||
![]() |
5349eb2302 | ||
![]() |
a8fd023398 | ||
![]() |
e34d48d913 | ||
![]() |
ee529c2276 | ||
![]() |
3d5e45c20a | ||
![]() |
b8283047ae | ||
![]() |
dad3a1ff28 | ||
![]() |
ce663398e6 | ||
![]() |
f5ec6de047 | ||
![]() |
807f788f92 | ||
![]() |
eaaaa575b8 | ||
![]() |
e21552e053 | ||
![]() |
6a7274c414 | ||
![]() |
35de04a2ce | ||
![]() |
a0c227fe55 | ||
![]() |
057ce29676 | ||
![]() |
982eeb0d24 | ||
![]() |
dfa25343a3 | ||
![]() |
dcfb4494c9 | ||
![]() |
5a1ef27224 | ||
![]() |
6f79ee9877 | ||
![]() |
bc0e420d67 | ||
![]() |
8e34756e6b | ||
![]() |
76951ea482 | ||
![]() |
b5e4aaa778 | ||
![]() |
39998cb34f | ||
![]() |
a771d2afd9 | ||
![]() |
dac3def6b9 | ||
![]() |
674b4a839c | ||
![]() |
037dcb6a11 | ||
![]() |
ad8c60d153 | ||
![]() |
3ea312e136 | ||
![]() |
d2a04743eb | ||
![]() |
b34f9c3b20 | ||
![]() |
fea7b0ec8c | ||
![]() |
0042e3eca4 | ||
![]() |
36db6f3d4b | ||
![]() |
3c633c2015 | ||
![]() |
dd8b51de67 | ||
![]() |
5fe846de1d | ||
![]() |
4711468598 | ||
![]() |
19dfaf1b94 | ||
![]() |
183ea24c9f | ||
![]() |
99693b6d30 | ||
![]() |
c0ad82b695 | ||
![]() |
fd36323d1c | ||
![]() |
4059c83a21 | ||
![]() |
b25c015516 | ||
![]() |
8fa52046e4 | ||
![]() |
15554322dd | ||
![]() |
0ee85aae21 | ||
![]() |
839fb34c8e | ||
![]() |
928580bf4f | ||
![]() |
9cca7aaa08 | ||
![]() |
38560cf13a | ||
![]() |
474ca08ef9 | ||
![]() |
d4fd529e49 | ||
![]() |
2260617447 | ||
![]() |
7c3ba3e518 | ||
![]() |
849e3a10ac | ||
![]() |
bdceeef3fb | ||
![]() |
8630e5f5b6 | ||
![]() |
2312eba5b6 | ||
![]() |
ad9d654886 | ||
![]() |
fa19a8975e | ||
![]() |
8987cd448f | ||
![]() |
a7536e3ebf | ||
![]() |
9c3bc2eb83 | ||
![]() |
d179efbd48 | ||
![]() |
801df5f7bd | ||
![]() |
d1c3ea7faa | ||
![]() |
dcfc53b7f2 | ||
![]() |
45002f8083 | ||
![]() |
722a2ca1e4 | ||
![]() |
2bd8b67d02 | ||
![]() |
637efd5cb3 | ||
![]() |
ad3dd76c2f | ||
![]() |
75aba12589 | ||
![]() |
ec33edb2f4 | ||
![]() |
4b706fa4dd | ||
![]() |
98799d9a69 | ||
![]() |
b80070ac0b | ||
![]() |
6b2e5559ca | ||
![]() |
82340ad6e3 | ||
![]() |
f45ab723ae | ||
![]() |
8e3ca37b05 | ||
![]() |
aef387ed69 | ||
![]() |
b93c970635 | ||
![]() |
a7d8b5c960 | ||
![]() |
56c9a3f270 | ||
![]() |
0c3dac45b5 | ||
![]() |
6965165c76 | ||
![]() |
73d33ff25a | ||
![]() |
df153be30e | ||
![]() |
9950ff2337 | ||
![]() |
9a9ab85baf | ||
![]() |
eaea42334b | ||
![]() |
de8ac013ee | ||
![]() |
9e2bf4820a | ||
![]() |
9af879a2bf | ||
![]() |
ff4203938b | ||
![]() |
a63f8809fa | ||
![]() |
edcde1f142 | ||
![]() |
6dc094f760 | ||
![]() |
186f520819 | ||
![]() |
0365fc5ac3 | ||
![]() |
61811a4bec | ||
![]() |
bb83c1eb0a | ||
![]() |
4ad4862641 | ||
![]() |
c03aa03ac2 | ||
![]() |
ada283441c | ||
![]() |
3cf73a77ac | ||
![]() |
71fedcb466 | ||
![]() |
1b9cf5121b | ||
![]() |
7fe76656f2 | ||
![]() |
0deb8a11d6 | ||
![]() |
064d384d97 | ||
![]() |
5045d06744 | ||
![]() |
c57aa81d15 | ||
![]() |
d35e350c79 | ||
![]() |
dd878c8d70 | ||
![]() |
9cd1945a89 | ||
![]() |
faab8a5560 | ||
![]() |
fcc9847bc3 | ||
![]() |
a64d457c30 | ||
![]() |
e799d757c2 | ||
![]() |
ac0ed0def8 | ||
![]() |
f01283c309 | ||
![]() |
a3c468a004 | ||
![]() |
3435ffd00c | ||
![]() |
4f1185c65d | ||
![]() |
2b1498cc6d | ||
![]() |
0643db4347 | ||
![]() |
29e6371cd1 | ||
![]() |
80c2d90e74 | ||
![]() |
f3cf608caa | ||
![]() |
c6d0557a3b | ||
![]() |
f3b7ae93f0 |
180
.devcontainer/Dockerfile
Normal file
180
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,180 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM --platform=$BUILDPLATFORM docker.io/node:20-bookworm-slim as main-app
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Buildx provided, must be defined to use though
|
||||
ARG TARGETARCH
|
||||
|
||||
# Can be workflow provided, defaults set for manual building
|
||||
ARG JBIG2ENC_VERSION=0.29
|
||||
ARG QPDF_VERSION=11.9.0
|
||||
ARG GS_VERSION=10.03.1
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
# Ignore warning from Whitenoise
|
||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||
PNGX_CONTAINERIZED=1
|
||||
|
||||
#
|
||||
# Begin installation and configuration
|
||||
# Order the steps below from least often changed to most
|
||||
#
|
||||
|
||||
# Packages need for running
|
||||
ARG RUNTIME_PACKAGES="\
|
||||
# General utils
|
||||
curl \
|
||||
# Docker specific
|
||||
gosu \
|
||||
# Timezones support
|
||||
tzdata \
|
||||
# fonts for text file thumbnail generation
|
||||
fonts-liberation \
|
||||
gettext \
|
||||
ghostscript \
|
||||
gnupg \
|
||||
icc-profiles-free \
|
||||
imagemagick \
|
||||
# PostgreSQL
|
||||
postgresql-client \
|
||||
# MySQL / MariaDB
|
||||
mariadb-client \
|
||||
# OCRmyPDF dependencies
|
||||
tesseract-ocr \
|
||||
tesseract-ocr-eng \
|
||||
tesseract-ocr-deu \
|
||||
tesseract-ocr-fra \
|
||||
tesseract-ocr-ita \
|
||||
tesseract-ocr-spa \
|
||||
unpaper \
|
||||
pngquant \
|
||||
jbig2dec \
|
||||
# lxml
|
||||
libxml2 \
|
||||
libxslt1.1 \
|
||||
# itself
|
||||
qpdf \
|
||||
# Mime type detection
|
||||
file \
|
||||
libmagic1 \
|
||||
media-types \
|
||||
zlib1g \
|
||||
# Barcode splitter
|
||||
libzbar0 \
|
||||
poppler-utils \
|
||||
htop \
|
||||
sudo"
|
||||
|
||||
# Install basic runtime packages.
|
||||
# These change very infrequently
|
||||
RUN set -eux \
|
||||
echo "Installing system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet --no-install-recommends ${RUNTIME_PACKAGES}
|
||||
|
||||
ARG PYTHON_PACKAGES="\
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-wheel \
|
||||
pipenv \
|
||||
ca-certificates"
|
||||
|
||||
RUN set -eux \
|
||||
echo "Installing python packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet ${PYTHON_PACKAGES}
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Installing pre-built updates" \
|
||||
&& echo "Installing qpdf ${QPDF_VERSION}" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/qpdf-${QPDF_VERSION}/qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./libqpdf29_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./qpdf_${QPDF_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing Ghostscript ${GS_VERSION}" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/ghostscript-${GS_VERSION}/libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
&& dpkg --install ./libgs10-common_${GS_VERSION}.dfsg-1_all.deb \
|
||||
&& dpkg --install ./libgs10_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./ghostscript_${GS_VERSION}.dfsg-1_${TARGETARCH}.deb \
|
||||
&& echo "Installing jbig2enc" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
https://github.com/paperless-ngx/builder/releases/download/jbig2enc-${JBIG2ENC_VERSION}/jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb \
|
||||
&& dpkg --install ./jbig2enc_${JBIG2ENC_VERSION}-1_${TARGETARCH}.deb
|
||||
|
||||
# setup docker-specific things
|
||||
# These change sometimes, but rarely
|
||||
WORKDIR /usr/src/paperless/src/docker/
|
||||
|
||||
COPY [ \
|
||||
"docker/imagemagick-policy.xml", \
|
||||
"./" \
|
||||
]
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Configuring ImageMagick" \
|
||||
&& mv imagemagick-policy.xml /etc/ImageMagick-6/policy.xml
|
||||
|
||||
# Packages needed only for building a few quick Python
|
||||
# dependencies
|
||||
ARG BUILD_PACKAGES="\
|
||||
build-essential \
|
||||
git \
|
||||
# https://www.psycopg.org/docs/install.html#prerequisites
|
||||
libpq-dev \
|
||||
# https://github.com/PyMySQL/mysqlclient#linux
|
||||
default-libmysqlclient-dev \
|
||||
pkg-config \
|
||||
pre-commit"
|
||||
|
||||
# hadolint ignore=DL3042
|
||||
RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
|
||||
set -eux \
|
||||
&& echo "Installing build system packages" \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet ${BUILD_PACKAGES}
|
||||
|
||||
RUN set -eux \
|
||||
&& npm update npm -g
|
||||
|
||||
# add users, setup scripts
|
||||
# Mount the compiled frontend to expected location
|
||||
RUN set -eux \
|
||||
&& echo "Setting up user/group" \
|
||||
&& groupmod --new-name paperless node \
|
||||
&& usermod --login paperless --home /usr/src/paperless node \
|
||||
&& usermod -s /bin/bash paperless \
|
||||
&& echo "paperless ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers \
|
||||
&& echo "Creating volume directories" \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/data \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/media \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/consume \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/export \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/paperless-ngx/.venv \
|
||||
&& echo "Adjusting all permissions" \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless
|
||||
# && echo "Collecting static files" \
|
||||
# && gosu paperless python3 manage.py collectstatic --clear --no-input --link \
|
||||
# && gosu paperless python3 manage.py compilemessages
|
||||
|
||||
VOLUME ["/usr/src/paperless/paperless-ngx/data", \
|
||||
"/usr/src/paperless/paperless-ngx/media", \
|
||||
"/usr/src/paperless/paperless-ngx/consume", \
|
||||
"/usr/src/paperless/paperless-ngx/export", \
|
||||
"/usr/src/paperless/paperless-ngx/.venv"]
|
117
.devcontainer/README.md
Normal file
117
.devcontainer/README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Paperless NGX Development Environment
|
||||
|
||||
## Overview
|
||||
|
||||
Welcome to the Paperless NGX development environment! This setup uses VSCode DevContainers to provide a consistent and seamless development experience.
|
||||
|
||||
### What are DevContainers?
|
||||
|
||||
DevContainers are a feature in VSCode that allows you to develop within a Docker container. This ensures that your development environment is consistent across different machines and setups. By defining a containerized environment, you can eliminate the "works on my machine" problem.
|
||||
|
||||
### Advantages of DevContainers
|
||||
|
||||
- **Consistency**: Same environment for all developers.
|
||||
- **Isolation**: Separate development environment from your local machine.
|
||||
- **Reproducibility**: Easily recreate the environment on any machine.
|
||||
- **Pre-configured Tools**: Include all necessary tools and dependencies in the container.
|
||||
|
||||
## DevContainer Setup
|
||||
|
||||
The DevContainer configuration provides up all the necessary services for Paperless NGX, including:
|
||||
|
||||
- Redis
|
||||
- Gotenberg
|
||||
- Tika
|
||||
|
||||
Data is stored using Docker volumes to ensure persistence across container restarts.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The setup includes debugging configurations (`launch.json`) and tasks (`tasks.json`) to help you manage and debug various parts of the project:
|
||||
|
||||
- **Backend Debugging:**
|
||||
- `manage.py runserver`
|
||||
- `manage.py document-consumer`
|
||||
- `celery`
|
||||
- **Maintenance Tasks:**
|
||||
- Create superuser
|
||||
- Run migrations
|
||||
- Recreate virtual environment (`.venv` with pipenv)
|
||||
- Compile frontend assets
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Step 1: Running the DevContainer
|
||||
|
||||
To start the DevContainer:
|
||||
|
||||
1. Open VSCode.
|
||||
2. Open the project folder.
|
||||
3. Open the command palette:
|
||||
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||
- **Mac**: `Cmd+Shift+P`
|
||||
4. Type and select `Dev Containers: Rebuild and Reopen in Container`.
|
||||
|
||||
VSCode will build and start the DevContainer environment.
|
||||
|
||||
### Step 2: Initial Setup
|
||||
|
||||
Once the DevContainer is up and running, perform the following steps:
|
||||
|
||||
1. **Compile Frontend Assets**:
|
||||
|
||||
- Open the command palette:
|
||||
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||
- **Mac**: `Cmd+Shift+P`
|
||||
- Select `Tasks: Run Task`.
|
||||
- Choose `Frontend Compile`.
|
||||
|
||||
2. **Run Database Migrations**:
|
||||
|
||||
- Open the command palette:
|
||||
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||
- **Mac**: `Cmd+Shift+P`
|
||||
- Select `Tasks: Run Task`.
|
||||
- Choose `Migrate Database`.
|
||||
|
||||
3. **Create Superuser**:
|
||||
- Open the command palette:
|
||||
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||
- **Mac**: `Cmd+Shift+P`
|
||||
- Select `Tasks: Run Task`.
|
||||
- Choose `Create Superuser`.
|
||||
|
||||
### Debugging and Running Services
|
||||
|
||||
You can start and debug backend services either as debugging sessions via `launch.json` or as tasks.
|
||||
|
||||
#### Using `launch.json`:
|
||||
|
||||
1. Press `F5` or go to the **Run and Debug** view in VSCode.
|
||||
2. Select the desired configuration:
|
||||
- `Runserver`
|
||||
- `Document Consumer`
|
||||
- `Celery`
|
||||
|
||||
#### Using Tasks:
|
||||
|
||||
1. Open the command palette:
|
||||
- **Windows/Linux**: `Ctrl+Shift+P`
|
||||
- **Mac**: `Cmd+Shift+P`
|
||||
2. Select `Tasks: Run Task`.
|
||||
3. Choose the desired task:
|
||||
- `Runserver`
|
||||
- `Document Consumer`
|
||||
- `Celery`
|
||||
|
||||
### Additional Maintenance Tasks
|
||||
|
||||
Additional tasks are available for common maintenance operations:
|
||||
|
||||
- **Recreate .venv**: For setting up the virtual environment using pipenv.
|
||||
- **Migrate Database**: To apply database migrations.
|
||||
- **Create Superuser**: To create an admin user for the application.
|
||||
|
||||
## Let's Get Started!
|
||||
|
||||
Follow the steps above to get your development environment up and running. Happy coding!
|
16
.devcontainer/devcontainer.json
Normal file
16
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Paperless Development",
|
||||
"dockerComposeFile": "docker-compose.devcontainer.sqlite-tika.yml",
|
||||
"service": "paperless-development",
|
||||
"workspaceFolder": "/usr/src/paperless/paperless-ngx",
|
||||
"postCreateCommand": "/bin/bash -c pre-commit install && pipenv install --dev",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"mhutchie.git-graph",
|
||||
"ms-python.python"
|
||||
]
|
||||
}
|
||||
},
|
||||
"remoteUser": "paperless"
|
||||
}
|
84
.devcontainer/docker-compose.devcontainer.sqlite-tika.yml
Normal file
84
.devcontainer/docker-compose.devcontainer.sqlite-tika.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
# Docker Compose file for developing Paperless NGX in VSCode DevContainers.
|
||||
# This file contains everything Paperless NGX needs to run.
|
||||
# Paperless supports amd64, arm, and arm64 hardware.
|
||||
# All compose files of Paperless configure it in the following way:
|
||||
#
|
||||
# - Paperless is (re)started on system boot if it was running before shutdown.
|
||||
# - Docker volumes for storing data are managed by Docker.
|
||||
# - Folders for importing and exporting files are created in the same directory
|
||||
# as this file and mounted to the correct folders inside the container.
|
||||
# - Paperless listens on port 8000.
|
||||
#
|
||||
# SQLite is used as the database. The SQLite file is stored in the data volume.
|
||||
#
|
||||
# In addition, this Docker Compose file adds the following optional
|
||||
# configurations:
|
||||
#
|
||||
# - Apache Tika and Gotenberg servers are started with Paperless NGX and Paperless
|
||||
# is configured to use these services. These provide support for consuming
|
||||
# Office documents (Word, Excel, PowerPoint, and their LibreOffice counterparts).
|
||||
#
|
||||
# This file is intended only to be used through VSCOde devcontainers. See README.md
|
||||
# in the folder .devcontainer.
|
||||
|
||||
|
||||
services:
|
||||
broker:
|
||||
image: docker.io/library/redis:7
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redisdata:/data
|
||||
|
||||
# No ports need to be exposed; the VSCode DevContainer plugin manages them.
|
||||
paperless-development:
|
||||
image: paperless-ngx
|
||||
build:
|
||||
context: ../ # Dockerfile cannot access files from parent directories if context is not set.
|
||||
dockerfile: ./.devcontainer/Dockerfile
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- broker
|
||||
- gotenberg
|
||||
- tika
|
||||
volumes:
|
||||
- ..:/usr/src/paperless/paperless-ngx:delegated
|
||||
- ../.devcontainer/vscode:/usr/src/paperless/paperless-ngx/.vscode:delegated # VSCode config files
|
||||
- pipenv:/usr/src/paperless/paperless-ngx/.venv # Pipenv environment persisted in volume
|
||||
- /usr/src/paperless/paperless-ngx/src/documents/static/frontend # Static frontend files exist only in container
|
||||
- /usr/src/paperless/paperless-ngx/src/.pytest_cache
|
||||
- /usr/src/paperless/paperless-ngx/.ruff_cache
|
||||
- /usr/src/paperless/paperless-ngx/htmlcov
|
||||
- /usr/src/paperless/paperless-ngx/.coverage
|
||||
- data:/usr/src/paperless/paperless-ngx/data
|
||||
- media:/usr/src/paperless/paperless-ngx/media
|
||||
environment:
|
||||
PAPERLESS_REDIS: redis://broker:6379
|
||||
PAPERLESS_TIKA_ENABLED: 1
|
||||
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
PAPERLESS_STATICDIR: ./src/documents/static
|
||||
PAPERLESS_DEBUG: true
|
||||
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: /bin/sh -c "chown -R paperless:paperless /usr/src/paperless/paperless-ngx/src/documents/static/frontend && chown -R paperless:paperless /usr/src/paperless/paperless-ngx/.ruff_cache && while sleep 1000; do :; done"
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
restart: unless-stopped
|
||||
|
||||
# The Gotenberg Chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even JavaScript.
|
||||
command:
|
||||
- "gotenberg"
|
||||
- "--chromium-disable-javascript=true"
|
||||
- "--chromium-allow-list=file:///tmp/.*"
|
||||
|
||||
tika:
|
||||
image: docker.io/apache/tika:latest
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
data:
|
||||
media:
|
||||
redisdata:
|
||||
pipenv:
|
43
.devcontainer/vscode/launch.json
Normal file
43
.devcontainer/vscode/launch.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "manage.py runserver",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/manage.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": ["runserver"],
|
||||
"django": true
|
||||
},
|
||||
{
|
||||
"name": "manage.py document_consumer",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/src/manage.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": ["document_consumer"],
|
||||
"django": true
|
||||
},
|
||||
{
|
||||
"name": "celery",
|
||||
"type": "python",
|
||||
"cwd": "${workspaceFolder}/src",
|
||||
"request": "launch",
|
||||
"module": "celery",
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceFolder}/src"
|
||||
},
|
||||
"args": [
|
||||
"-A",
|
||||
"paperless",
|
||||
"worker",
|
||||
"-l",
|
||||
"DEBUG"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
11
.devcontainer/vscode/settings.json
Normal file
11
.devcontainer/vscode/settings.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"python.testing.pytestArgs": [
|
||||
"src"
|
||||
],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"files.watcherExclude": {
|
||||
"**/.venv/**": true,
|
||||
"**/pytest_cache/**": true
|
||||
}
|
||||
}
|
136
.devcontainer/vscode/tasks.json
Normal file
136
.devcontainer/vscode/tasks.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "manage.py document_consumer",
|
||||
"type": "shell",
|
||||
"command": "pipenv run python manage.py document_consumer",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"label": "manage.py runserver",
|
||||
"type": "shell",
|
||||
"command": "pipenv run python manage.py runserver",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: manage.py migrate",
|
||||
"type": "shell",
|
||||
"command": "pipenv run python manage.py migrate",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: manage.py createsuperuser",
|
||||
"type": "shell",
|
||||
"command": "pipenv run python manage.py createsuperuser",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "compile frontend",
|
||||
"type": "shell",
|
||||
"command": "npm ci && ./node_modules/.bin/ng build --configuration production",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src-ui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Maintenance: recreate .venv",
|
||||
"type": "shell",
|
||||
"command": "rm -R -v .venv/* || pipenv install --dev",
|
||||
"group": "none",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Celery Worker",
|
||||
"type": "shell",
|
||||
"command": "pipenv run celery --app paperless worker -l DEBUG",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true,
|
||||
"revealProblems": "onProblem"
|
||||
},
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/src"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -110,6 +110,8 @@ body:
|
||||
options:
|
||||
- label: I believe this issue is a bug that affects all users of Paperless-ngx, not something specific to my installation.
|
||||
required: true
|
||||
- label: This issue is not about the OCR or archive creation of a specific file(s). Otherwise, please see above regarding OCR tools.
|
||||
required: true
|
||||
- label: I have already searched for relevant existing issues and discussions before opening this report.
|
||||
required: true
|
||||
- label: I have updated the title field above with a concise description.
|
||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -49,10 +49,6 @@ updates:
|
||||
- "paperless-ngx/backend"
|
||||
ignore:
|
||||
- dependency-name: "uvicorn"
|
||||
- dependency-name: "djangorestframework"
|
||||
versions:
|
||||
- "3.15.0"
|
||||
- "3.15.1"
|
||||
groups:
|
||||
development:
|
||||
patterns:
|
||||
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -398,7 +398,7 @@ jobs:
|
||||
password: ${{ secrets.QUAY_ROBOT_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -406,6 +406,8 @@ jobs:
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.docker-meta.outputs.tags }}
|
||||
labels: ${{ steps.docker-meta.outputs.labels }}
|
||||
build-args: |
|
||||
PNGX_TAG_VERSION=${{ steps.docker-meta.outputs.version }}
|
||||
# Get cache layers from this branch, then dev
|
||||
# This allows new branches to get at least some cache benefits, generally from dev
|
||||
cache-from: |
|
||||
|
4
.github/workflows/cleanup-tags.yml
vendored
4
.github/workflows/cleanup-tags.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
-
|
||||
name: Clean temporary images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.7.0
|
||||
uses: stumpylog/image-cleaner-action/ephemeral@v0.8.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
-
|
||||
name: Clean untagged images
|
||||
if: "${{ env.TOKEN != '' }}"
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.7.0
|
||||
uses: stumpylog/image-cleaner-action/untagged@v0.8.0
|
||||
with:
|
||||
token: "${{ env.TOKEN }}"
|
||||
owner: "${{ github.repository_owner }}"
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -66,6 +66,8 @@ target/
|
||||
.vscode
|
||||
/src-ui/.vscode
|
||||
/docs/.vscode
|
||||
.vscode-server
|
||||
*CommandMarker
|
||||
|
||||
# Other stuff that doesn't belong
|
||||
.virtualenv
|
||||
|
@@ -36,8 +36,9 @@ repos:
|
||||
exclude_types:
|
||||
- pofile
|
||||
- json
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: 'v3.1.0'
|
||||
# See https://github.com/prettier/prettier/issues/15742 for the fork reason
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: 'v3.3.3'
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or:
|
||||
@@ -47,7 +48,7 @@ repos:
|
||||
exclude: "(^Pipfile\\.lock$)"
|
||||
# Python hooks
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.4.9'
|
||||
rev: 'v0.6.4'
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
@@ -1 +1 @@
|
||||
3.9.18
|
||||
3.9.19
|
||||
|
22
Dockerfile
22
Dockerfile
@@ -13,6 +13,16 @@ WORKDIR /src/src-ui
|
||||
RUN set -eux \
|
||||
&& npm update npm -g \
|
||||
&& npm ci
|
||||
|
||||
ARG PNGX_TAG_VERSION=
|
||||
# Add the tag to the environment file if its a tagged dev build
|
||||
RUN set -eux && \
|
||||
case "${PNGX_TAG_VERSION}" in \
|
||||
dev|fix*|feature*) \
|
||||
sed -i -E "s/version: '([0-9\.]+)'/version: '\1 #${PNGX_TAG_VERSION}'/g" /src/src-ui/src/environments/environment.prod.ts \
|
||||
;; \
|
||||
esac
|
||||
|
||||
RUN set -eux \
|
||||
&& ./node_modules/.bin/ng build --configuration production
|
||||
|
||||
@@ -223,11 +233,11 @@ RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade wheel \
|
||||
&& echo "Installing Python requirements" \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output psycopg_c-3.1.19-cp311-cp311-linux_x86_64.whl \
|
||||
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.1.19/psycopg_c-3.1.19-cp311-cp311-linux_x86_64.whl \
|
||||
--output psycopg_c-3.2.1-cp311-cp311-linux_x86_64.whl \
|
||||
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.1/psycopg_c-3.2.1-cp311-cp311-linux_x86_64.whl \
|
||||
&& curl --fail --silent --show-error --location \
|
||||
--output psycopg_c-3.1.19-cp311-cp311-linux_aarch64.whl \
|
||||
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.1.19/psycopg_c-3.1.19-cp311-cp311-linux_aarch64.whl \
|
||||
--output psycopg_c-3.2.1-cp311-cp311-linux_aarch64.whl \
|
||||
https://github.com/paperless-ngx/builder/releases/download/psycopg-3.2.1/psycopg_c-3.2.1-cp311-cp311-linux_aarch64.whl \
|
||||
&& python3 -m pip install --default-timeout=1000 --find-links . --requirement requirements.txt \
|
||||
&& echo "Patching whitenoise for compression speedup" \
|
||||
&& curl --fail --silent --show-error --location --output 484.patch https://github.com/evansd/whitenoise/pull/484.patch \
|
||||
@@ -236,7 +246,7 @@ RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \
|
||||
&& echo "Installing NLTK data" \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" snowball_data \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" stopwords \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" punkt \
|
||||
&& python3 -W ignore::RuntimeWarning -m nltk.downloader -d "/usr/share/nltk_data" punkt_tab \
|
||||
&& echo "Cleaning up image" \
|
||||
&& apt-get --yes purge ${BUILD_PACKAGES} \
|
||||
&& apt-get --yes autoremove --purge \
|
||||
@@ -265,6 +275,8 @@ RUN set -eux \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/media \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/consume \
|
||||
&& mkdir --parents --verbose /usr/src/paperless/export \
|
||||
&& echo "Creating gnupg directory" \
|
||||
&& mkdir -m700 --verbose /usr/src/paperless/.gnupg \
|
||||
&& echo "Adjusting all permissions" \
|
||||
&& chown --from root:root --changes --recursive paperless:paperless /usr/src/paperless \
|
||||
&& echo "Collecting static files" \
|
||||
|
12
Pipfile
12
Pipfile
@@ -7,18 +7,18 @@ name = "pypi"
|
||||
dateparser = "~=1.2"
|
||||
# WARNING: django does not use semver.
|
||||
# Only patch versions are guaranteed to not introduce breaking changes.
|
||||
django = "~=4.2.13"
|
||||
django = "~=4.2.15"
|
||||
django-allauth = {extras = ["socialaccount"], version = "*"}
|
||||
django-auditlog = "*"
|
||||
django-celery-results = "*"
|
||||
django-compression-middleware = "*"
|
||||
django-cors-headers = "*"
|
||||
django-extensions = "*"
|
||||
django-filter = "~=24.2"
|
||||
django-filter = "~=24.3"
|
||||
django-guardian = "*"
|
||||
django-multiselectfield = "*"
|
||||
django-soft-delete = "*"
|
||||
djangorestframework = "==3.14.0"
|
||||
djangorestframework = "==3.15.2"
|
||||
djangorestframework-guardian = "*"
|
||||
drf-writable-nested = "*"
|
||||
bleach = "*"
|
||||
@@ -54,8 +54,8 @@ tqdm = "*"
|
||||
# See https://github.com/paperless-ngx/paperless-ngx/issues/5494
|
||||
uvicorn = {extras = ["standard"], version = "==0.25.0"}
|
||||
watchdog = "~=4.0"
|
||||
whitenoise = "~=6.6"
|
||||
whoosh="~=2.7"
|
||||
whitenoise = "~=6.7"
|
||||
whoosh = "~=2.7"
|
||||
zxing-cpp = {version = "*", platform_machine = "== 'x86_64'"}
|
||||
|
||||
[dev-packages]
|
||||
@@ -71,6 +71,7 @@ pytest-httpx = "*"
|
||||
pytest-env = "*"
|
||||
pytest-sugar = "*"
|
||||
pytest-xdist = "*"
|
||||
pytest-mock = "*"
|
||||
pytest-rerunfailures = "*"
|
||||
imagehash = "*"
|
||||
daphne = "*"
|
||||
@@ -93,5 +94,4 @@ types-tqdm = "*"
|
||||
types-Markdown = "*"
|
||||
types-Pygments = "*"
|
||||
types-colorama = "*"
|
||||
types-psycopg2 = "*"
|
||||
types-setuptools = "*"
|
||||
|
3976
Pipfile.lock
generated
3976
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,7 @@ Thanks to the generous folks at [DigitalOcean](https://m.do.co/c/8d70b916d462),
|
||||
<a href="https://m.do.co/c/8d70b916d462" style="padding-top: 4px; display: block;">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_white.svg" width="140px">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="140px">
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_black_.svg" width="140px">
|
||||
</picture>
|
||||
</a>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
services:
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
image: docker.io/gotenberg/gotenberg:8.7
|
||||
hostname: gotenberg
|
||||
container_name: gotenberg
|
||||
network_mode: host
|
||||
|
@@ -77,7 +77,7 @@ services:
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
image: docker.io/gotenberg/gotenberg:8.7
|
||||
restart: unless-stopped
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
# want to allow external content like tracking pixels or even javascript.
|
||||
|
@@ -71,7 +71,7 @@ services:
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
image: docker.io/gotenberg/gotenberg:8.7
|
||||
restart: unless-stopped
|
||||
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
|
@@ -59,7 +59,7 @@ services:
|
||||
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
|
||||
|
||||
gotenberg:
|
||||
image: docker.io/gotenberg/gotenberg:7.10
|
||||
image: docker.io/gotenberg/gotenberg:8.7
|
||||
restart: unless-stopped
|
||||
|
||||
# The gotenberg chromium route is used to convert .eml files. We do not
|
||||
|
@@ -10,8 +10,8 @@ map_uidgid() {
|
||||
local -r usermap_new_gid=${USERMAP_GID:-${usermap_original_gid:-$usermap_new_uid}}
|
||||
if [[ ${usermap_new_uid} != "${usermap_original_uid}" || ${usermap_new_gid} != "${usermap_original_gid}" ]]; then
|
||||
echo "Mapping UID and GID for paperless:paperless to $usermap_new_uid:$usermap_new_gid"
|
||||
usermod -o -u "${usermap_new_uid}" paperless
|
||||
groupmod -o -g "${usermap_new_gid}" paperless
|
||||
usermod --non-unique --uid "${usermap_new_uid}" paperless
|
||||
groupmod --non-unique --gid "${usermap_new_gid}" paperless
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ custom_container_init() {
|
||||
fi
|
||||
|
||||
# Make sure custom init directory has files in it
|
||||
if [ -n "$(/bin/ls -A "${custom_script_dir}" 2>/dev/null)" ]; then
|
||||
if [ -n "$(/bin/ls --almost-all "${custom_script_dir}" 2>/dev/null)" ]; then
|
||||
echo "[custom-init] files found in ${custom_script_dir} executing"
|
||||
# Loop over files in the directory
|
||||
for SCRIPT in "${custom_script_dir}"/*; do
|
||||
@@ -86,13 +86,13 @@ initialize() {
|
||||
"${CONSUME_DIR}"; do
|
||||
if [[ ! -d "${dir}" ]]; then
|
||||
echo "Creating directory ${dir}"
|
||||
mkdir --parents "${dir}"
|
||||
mkdir --parents --verbose "${dir}"
|
||||
fi
|
||||
done
|
||||
|
||||
local -r tmp_dir="${PAPERLESS_SCRATCH_DIR:=/tmp/paperless}"
|
||||
echo "Creating directory scratch directory ${tmp_dir}"
|
||||
mkdir --parents "${tmp_dir}"
|
||||
mkdir --parents --verbose "${tmp_dir}"
|
||||
|
||||
set +e
|
||||
echo "Adjusting permissions of paperless files. This may take a while."
|
||||
@@ -102,7 +102,7 @@ initialize() {
|
||||
"${DATA_DIR}" \
|
||||
"${MEDIA_ROOT_DIR}" \
|
||||
"${CONSUME_DIR}"; do
|
||||
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} +
|
||||
find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown --changes paperless:paperless {} +
|
||||
done
|
||||
set -e
|
||||
|
||||
@@ -127,7 +127,7 @@ install_languages() {
|
||||
for lang in "${langs[@]}"; do
|
||||
pkg="tesseract-ocr-$lang"
|
||||
|
||||
if dpkg -s "$pkg" &>/dev/null; then
|
||||
if dpkg --status "$pkg" &>/dev/null; then
|
||||
echo "Package $pkg already installed!"
|
||||
continue
|
||||
fi
|
||||
@@ -138,7 +138,7 @@ install_languages() {
|
||||
fi
|
||||
|
||||
echo "Installing package $pkg..."
|
||||
if ! apt-get -y install "$pkg" &>/dev/null; then
|
||||
if ! apt-get --assume-yes install "$pkg" &>/dev/null; then
|
||||
echo "Could not install $pkg"
|
||||
exit 1
|
||||
fi
|
||||
@@ -148,7 +148,7 @@ install_languages() {
|
||||
echo "Paperless-ngx docker container starting..."
|
||||
|
||||
gosu_cmd=(gosu paperless)
|
||||
if [ "$(id -u)" == "$(id -u paperless)" ]; then
|
||||
if [ "$(id --user)" == "$(id --user paperless)" ]; then
|
||||
gosu_cmd=()
|
||||
fi
|
||||
|
||||
|
@@ -13,7 +13,7 @@ wait_for_postgres() {
|
||||
|
||||
# Disable warning, host and port can't have spaces
|
||||
# shellcheck disable=SC2086
|
||||
while [ ! "$(pg_isready -h ${host} -p ${port})" ]; do
|
||||
while [ ! "$(pg_isready --host ${host} --port ${port})" ]; do
|
||||
|
||||
if [ $attempt_num -eq $max_attempts ]; then
|
||||
echo "Unable to connect to database."
|
||||
@@ -25,6 +25,7 @@ wait_for_postgres() {
|
||||
attempt_num=$(("$attempt_num" + 1))
|
||||
sleep 5
|
||||
done
|
||||
echo "Connected to PostgreSQL"
|
||||
}
|
||||
|
||||
wait_for_mariadb() {
|
||||
@@ -51,6 +52,7 @@ wait_for_mariadb() {
|
||||
attempt_num=$(("$attempt_num" + 1))
|
||||
sleep 5
|
||||
done
|
||||
echo "Connected to MariaDB"
|
||||
}
|
||||
|
||||
wait_for_redis() {
|
||||
|
@@ -248,6 +248,7 @@ optional arguments:
|
||||
-z, --zip
|
||||
-zn, --zip-name
|
||||
--data-only
|
||||
--no-progress-bar
|
||||
--passphrase
|
||||
```
|
||||
|
||||
@@ -310,6 +311,10 @@ value set in `-zn` or `--zip-name`.
|
||||
If `--data-only` is provided, only the database will be exported. This option is intended
|
||||
to facilitate database upgrades without needing to clean documents and thumbnails from the media directory.
|
||||
|
||||
If `--no-progress-bar` is provided, the progress bar will be hidden, rendering the
|
||||
exporter quiet. This option is useful for scripting scenarios, such as when using the
|
||||
exporter with `crontab`.
|
||||
|
||||
If `--passphrase` is provided, it will be used to encrypt certain fields in the export. This value
|
||||
must be provided to import. If this value is lost, the export cannot be imported.
|
||||
|
||||
@@ -331,11 +336,12 @@ and the script does the rest of the work:
|
||||
document_importer source
|
||||
```
|
||||
|
||||
| Option | Required | Default | Description |
|
||||
| -------------- | -------- | ------- | ------------------------------------------------------------------------- |
|
||||
| source | Yes | N/A | The directory containing an export |
|
||||
| `--data-only` | No | False | If provided, only import data, do not import document files or thumbnails |
|
||||
| `--passphrase` | No | N/A | If your export was encrypted with a passphrase, must be provided |
|
||||
| Option | Required | Default | Description |
|
||||
| ------------------- | -------- | ------- | ------------------------------------------------------------------------- |
|
||||
| source | Yes | N/A | The directory containing an export |
|
||||
| `--no-progress-bar` | No | False | If provided, the progress bar will be hidden |
|
||||
| `--data-only` | No | False | If provided, only import data, do not import document files or thumbnails |
|
||||
| `--passphrase` | No | N/A | If your export was encrypted with a passphrase, must be provided |
|
||||
|
||||
When you use the provided docker compose script, put the export inside
|
||||
the `export` folder in your paperless source directory. Specify
|
||||
|
@@ -36,7 +36,7 @@ The following algorithms are available:
|
||||
- **Regular expression:** Parses the match as a regular expression and
|
||||
tries to find a match within the document.
|
||||
- **Fuzzy match:** Uses a partial matching based on locating the tag text
|
||||
inside the document, using a [partial ratio](https://maxbachmann.github.io/RapidFuzz/Usage/fuzz.html#partial-ratio)
|
||||
inside the document, using a [partial ratio](https://rapidfuzz.github.io/RapidFuzz/Usage/fuzz.html#partial-ratio)
|
||||
- **Auto:** Tries to automatically match new documents. This does not
|
||||
require you to set a match. See the [notes below](#automatic-matching).
|
||||
|
||||
@@ -187,6 +187,7 @@ variables:
|
||||
| `DOCUMENT_THUMBNAIL_PATH` | Path to the generated thumbnail |
|
||||
| `DOCUMENT_DOWNLOAD_URL` | URL for document download |
|
||||
| `DOCUMENT_THUMBNAIL_URL` | URL for the document thumbnail |
|
||||
| `DOCUMENT_OWNER` | Username of the document owner (if any) |
|
||||
| `DOCUMENT_CORRESPONDENT` | Assigned correspondent (if any) |
|
||||
| `DOCUMENT_TAGS` | Comma separated list of tags applied (if any) |
|
||||
| `DOCUMENT_ORIGINAL_FILENAME` | Filename of original document |
|
||||
@@ -687,4 +688,59 @@ More details about configuration option for various providers can be found in th
|
||||
|
||||
### Disabling Regular Login
|
||||
|
||||
Once external auth is set up, 'regular' login can be disabled with the [PAPERLESS_DISABLE_REGULAR_LOGIN](configuration.md#PAPERLESS_DISABLE_REGULAR_LOGIN) setting.
|
||||
Once external auth is set up, 'regular' login can be disabled with the [PAPERLESS_DISABLE_REGULAR_LOGIN](configuration.md#PAPERLESS_DISABLE_REGULAR_LOGIN) setting and / or users can be automatically
|
||||
redirected with the [PAPERLESS_REDIRECT_LOGIN_TO_SSO](configuration.md#PAPERLESS_REDIRECT_LOGIN_TO_SSO) setting.
|
||||
|
||||
## Decryption of encrypted emails before consumption {#gpg-decryptor}
|
||||
|
||||
Paperless-ngx can be configured to decrypt gpg encrypted emails before consumption.
|
||||
|
||||
### Requirements
|
||||
|
||||
You need a recent version of `gpg-agent >= 2.1.1` installed on your host.
|
||||
Your host needs to be setup for decrypting your emails via `gpg-agent`, see this [tutorial](https://www.digitalocean.com/community/tutorials/how-to-use-gpg-to-encrypt-and-sign-messages#encrypt-and-decrypt-messages-with-gpg) for instance.
|
||||
Test your setup and make sure that you can encrypt and decrypt files using your key
|
||||
|
||||
```
|
||||
gpg --encrypt --armor -r person@email.com name_of_file
|
||||
gpg --decrypt name_of_file.asc
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
First, enable the [PAPERLESS_GPG_DECRYPTOR environment variable](configuration.md#PAPERLESS_GPG_DECRYPTOR).
|
||||
|
||||
Then determine your local `gpg-agent.extra` socket by invoking
|
||||
|
||||
```
|
||||
gpgconf --list-dir agent-extra-socket
|
||||
```
|
||||
|
||||
on your host. A possible output is `~/.gnupg/S.gpg-agent.extra`.
|
||||
Also find the location of your public keyring.
|
||||
|
||||
If using docker, you'll need to add the following volume mounts to your `docker-compose.yml` file:
|
||||
|
||||
```yaml
|
||||
webserver:
|
||||
volumes:
|
||||
- /home/user/.gnupg/pubring.gpg:/usr/src/paperless/.gnupg/pubring.gpg
|
||||
- <path to gpg-agent.extra socket>:/usr/src/paperless/.gnupg/S.gpg-agent
|
||||
```
|
||||
|
||||
For a 'bare-metal' installation no further configuration is necessary. If you
|
||||
want to use a separate `GNUPG_HOME`, you can do so by configuring the [PAPERLESS_EMAIL_GNUPG_HOME environment variable](configuration.md#PAPERLESS_EMAIL_GNUPG_HOME).
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- Make sure, that `gpg-agent` is running on your host machine
|
||||
- Make sure, that encryption and decryption works from inside the container using the `gpg` commands from above.
|
||||
- Check that all files in `/usr/src/paperless/.gnupg` have correct permissions
|
||||
|
||||
```shell
|
||||
paperless@9da1865df327:~/.gnupg$ ls -al
|
||||
drwx------ 1 paperless paperless 4096 Aug 18 17:52 .
|
||||
drwxr-xr-x 1 paperless paperless 4096 Aug 18 17:52 ..
|
||||
srw------- 1 paperless paperless 0 Aug 18 17:22 S.gpg-agent
|
||||
-rw------- 1 paperless paperless 147940 Jul 24 10:23 pubring.gpg
|
||||
```
|
||||
|
@@ -1,5 +1,282 @@
|
||||
# Changelog
|
||||
|
||||
## paperless-ngx 2.11.6
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: fix nltk tokenizer breaking change [@shamoon](https://github.com/shamoon) ([#7522](https://github.com/paperless-ngx/paperless-ngx/pull/7522))
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>1 change</summary>
|
||||
|
||||
- Fix: fix nltk tokenizer breaking change [@shamoon](https://github.com/shamoon) ([#7522](https://github.com/paperless-ngx/paperless-ngx/pull/7522))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.11.5
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: use JSON for update archive file auditlog entries [@shamoon](https://github.com/shamoon) ([#7503](https://github.com/paperless-ngx/paperless-ngx/pull/7503))
|
||||
- Fix: respect deskew / rotate pages from AppConfig if set [@shamoon](https://github.com/shamoon) ([#7501](https://github.com/paperless-ngx/paperless-ngx/pull/7501))
|
||||
|
||||
### Dependencies
|
||||
|
||||
<details>
|
||||
<summary>5 changes</summary>
|
||||
|
||||
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates [@dependabot](https://github.com/dependabot) ([#7502](https://github.com/paperless-ngx/paperless-ngx/pull/7502))
|
||||
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7497](https://github.com/paperless-ngx/paperless-ngx/pull/7497))
|
||||
- Chore(deps-dev): Bump axios from 1.6.7 to 1.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#7472](https://github.com/paperless-ngx/paperless-ngx/pull/7472))
|
||||
- Chore(deps-dev): Bump ruff from 0.5.6 to 0.5.7 in the development group [@dependabot](https://github.com/dependabot) ([#7457](https://github.com/paperless-ngx/paperless-ngx/pull/7457))
|
||||
- Chore(deps): Bump the small-changes group with 3 updates [@dependabot](https://github.com/dependabot) ([#7460](https://github.com/paperless-ngx/paperless-ngx/pull/7460))
|
||||
</details>
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>7 changes</summary>
|
||||
|
||||
- Fix: use JSON for update archive file auditlog entries [@shamoon](https://github.com/shamoon) ([#7503](https://github.com/paperless-ngx/paperless-ngx/pull/7503))
|
||||
- Chore(deps): Bump the small-changes group across 1 directory with 6 updates [@dependabot](https://github.com/dependabot) ([#7502](https://github.com/paperless-ngx/paperless-ngx/pull/7502))
|
||||
- Fix: respect deskew / rotate pages from AppConfig if set [@shamoon](https://github.com/shamoon) ([#7501](https://github.com/paperless-ngx/paperless-ngx/pull/7501))
|
||||
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7497](https://github.com/paperless-ngx/paperless-ngx/pull/7497))
|
||||
- Chore(deps-dev): Bump axios from 1.6.7 to 1.7.4 in /src-ui [@dependabot](https://github.com/dependabot) ([#7472](https://github.com/paperless-ngx/paperless-ngx/pull/7472))
|
||||
- Chore(deps-dev): Bump ruff from 0.5.6 to 0.5.7 in the development group [@dependabot](https://github.com/dependabot) ([#7457](https://github.com/paperless-ngx/paperless-ngx/pull/7457))
|
||||
- Chore(deps): Bump the small-changes group with 3 updates [@dependabot](https://github.com/dependabot) ([#7460](https://github.com/paperless-ngx/paperless-ngx/pull/7460))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.11.4
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: initial upload message not being dismissed [@shamoon](https://github.com/shamoon) ([#7438](https://github.com/paperless-ngx/paperless-ngx/pull/7438))
|
||||
|
||||
### All App Changes
|
||||
|
||||
- Fix: initial upload message not being dismissed [@shamoon](https://github.com/shamoon) ([#7438](https://github.com/paperless-ngx/paperless-ngx/pull/7438))
|
||||
|
||||
## paperless-ngx 2.11.3
|
||||
|
||||
### Features
|
||||
|
||||
- Enhancement: optimize tasks / stats reload [@shamoon](https://github.com/shamoon) ([#7402](https://github.com/paperless-ngx/paperless-ngx/pull/7402))
|
||||
- Enhancement: allow specifying default currency for Monetary custom field [@shamoon](https://github.com/shamoon) ([#7381](https://github.com/paperless-ngx/paperless-ngx/pull/7381))
|
||||
- Enhancement: specify when pre-check fails for documents in trash [@shamoon](https://github.com/shamoon) ([#7355](https://github.com/paperless-ngx/paperless-ngx/pull/7355))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: clear selection after reload for management lists [@shamoon](https://github.com/shamoon) ([#7421](https://github.com/paperless-ngx/paperless-ngx/pull/7421))
|
||||
- Fix: disable inline create buttons if insufficient permissions [@shamoon](https://github.com/shamoon) ([#7401](https://github.com/paperless-ngx/paperless-ngx/pull/7401))
|
||||
- Fix: use entire document for dropzone [@shamoon](https://github.com/shamoon) ([#7342](https://github.com/paperless-ngx/paperless-ngx/pull/7342))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Chore(deps): Bump stumpylog/image-cleaner-action from 0.7.0 to 0.8.0 in the actions group [@dependabot](https://github.com/dependabot) ([#7371](https://github.com/paperless-ngx/paperless-ngx/pull/7371))
|
||||
|
||||
### Dependencies
|
||||
|
||||
<details>
|
||||
<summary>11 changes</summary>
|
||||
|
||||
- Chore(deps): Bump django from 4.2.14 to 4.2.15 [@dependabot](https://github.com/dependabot) ([#7412](https://github.com/paperless-ngx/paperless-ngx/pull/7412))
|
||||
- Chore(deps-dev): Bump the development group with 3 updates [@dependabot](https://github.com/dependabot) ([#7394](https://github.com/paperless-ngx/paperless-ngx/pull/7394))
|
||||
- Chore(deps): Bump the small-changes group with 5 updates [@dependabot](https://github.com/dependabot) ([#7397](https://github.com/paperless-ngx/paperless-ngx/pull/7397))
|
||||
- Chore(deps-dev): Bump [@<!---->playwright/test from 1.42.1 to 1.45.3 in /src-ui @dependabot](https://github.com/<!---->playwright/test from 1.42.1 to 1.45.3 in /src-ui @dependabot) ([#7367](https://github.com/paperless-ngx/paperless-ngx/pull/7367))
|
||||
- Chore(deps-dev): Bump [@<!---->types/node from 20.12.2 to 22.0.2 in /src-ui @dependabot](https://github.com/<!---->types/node from 20.12.2 to 22.0.2 in /src-ui @dependabot) ([#7366](https://github.com/paperless-ngx/paperless-ngx/pull/7366))
|
||||
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates [@dependabot](https://github.com/dependabot) ([#7365](https://github.com/paperless-ngx/paperless-ngx/pull/7365))
|
||||
- Chore(deps): Bump uuid from 9.0.1 to 10.0.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#7370](https://github.com/paperless-ngx/paperless-ngx/pull/7370))
|
||||
- Chore(deps): Bump stumpylog/image-cleaner-action from 0.7.0 to 0.8.0 in the actions group [@dependabot](https://github.com/dependabot) ([#7371](https://github.com/paperless-ngx/paperless-ngx/pull/7371))
|
||||
- Chore(deps): Bump zone.js from 0.14.4 to 0.14.8 in /src-ui [@dependabot](https://github.com/dependabot) ([#7368](https://github.com/paperless-ngx/paperless-ngx/pull/7368))
|
||||
- Chore(deps-dev): Bump jest-preset-angular from 14.1.1 to 14.2.2 in /src-ui in the frontend-jest-dependencies group [@dependabot](https://github.com/dependabot) ([#7364](https://github.com/paperless-ngx/paperless-ngx/pull/7364))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 20 updates [@dependabot](https://github.com/dependabot) ([#7363](https://github.com/paperless-ngx/paperless-ngx/pull/7363))
|
||||
</details>
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>15 changes</summary>
|
||||
|
||||
- Fix: clear selection after reload for management lists [@shamoon](https://github.com/shamoon) ([#7421](https://github.com/paperless-ngx/paperless-ngx/pull/7421))
|
||||
- Enhancement: optimize tasks / stats reload [@shamoon](https://github.com/shamoon) ([#7402](https://github.com/paperless-ngx/paperless-ngx/pull/7402))
|
||||
- Enhancement: allow specifying default currency for Monetary custom field [@shamoon](https://github.com/shamoon) ([#7381](https://github.com/paperless-ngx/paperless-ngx/pull/7381))
|
||||
- Enhancement: specify when pre-check fails for documents in trash [@shamoon](https://github.com/shamoon) ([#7355](https://github.com/paperless-ngx/paperless-ngx/pull/7355))
|
||||
- Chore(deps-dev): Bump the development group with 3 updates [@dependabot](https://github.com/dependabot) ([#7394](https://github.com/paperless-ngx/paperless-ngx/pull/7394))
|
||||
- Fix: disable inline create buttons if insufficient permissions [@shamoon](https://github.com/shamoon) ([#7401](https://github.com/paperless-ngx/paperless-ngx/pull/7401))
|
||||
- Chore(deps): Bump the small-changes group with 5 updates [@dependabot](https://github.com/dependabot) ([#7397](https://github.com/paperless-ngx/paperless-ngx/pull/7397))
|
||||
- Chore(deps-dev): Bump [@<!---->playwright/test from 1.42.1 to 1.45.3 in /src-ui @dependabot](https://github.com/<!---->playwright/test from 1.42.1 to 1.45.3 in /src-ui @dependabot) ([#7367](https://github.com/paperless-ngx/paperless-ngx/pull/7367))
|
||||
- Chore(deps-dev): Bump [@<!---->types/node from 20.12.2 to 22.0.2 in /src-ui @dependabot](https://github.com/<!---->types/node from 20.12.2 to 22.0.2 in /src-ui @dependabot) ([#7366](https://github.com/paperless-ngx/paperless-ngx/pull/7366))
|
||||
- Chore(deps-dev): Bump the frontend-eslint-dependencies group in /src-ui with 4 updates [@dependabot](https://github.com/dependabot) ([#7365](https://github.com/paperless-ngx/paperless-ngx/pull/7365))
|
||||
- Chore(deps): Bump uuid from 9.0.1 to 10.0.0 in /src-ui [@dependabot](https://github.com/dependabot) ([#7370](https://github.com/paperless-ngx/paperless-ngx/pull/7370))
|
||||
- Chore(deps): Bump zone.js from 0.14.4 to 0.14.8 in /src-ui [@dependabot](https://github.com/dependabot) ([#7368](https://github.com/paperless-ngx/paperless-ngx/pull/7368))
|
||||
- Chore(deps-dev): Bump jest-preset-angular from 14.1.1 to 14.2.2 in /src-ui in the frontend-jest-dependencies group [@dependabot](https://github.com/dependabot) ([#7364](https://github.com/paperless-ngx/paperless-ngx/pull/7364))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 20 updates [@dependabot](https://github.com/dependabot) ([#7363](https://github.com/paperless-ngx/paperless-ngx/pull/7363))
|
||||
- Fix: use entire document for dropzone [@shamoon](https://github.com/shamoon) ([#7342](https://github.com/paperless-ngx/paperless-ngx/pull/7342))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.11.2
|
||||
|
||||
### Changes
|
||||
|
||||
- Change: more clearly handle init permissions error [@shamoon](https://github.com/shamoon) ([#7334](https://github.com/paperless-ngx/paperless-ngx/pull/7334))
|
||||
- Chore: add permissions info link from webUI [@shamoon](https://github.com/shamoon) ([#7310](https://github.com/paperless-ngx/paperless-ngx/pull/7310))
|
||||
- Fix: increase search input text contrast with light custom theme colors [@JayBkr](https://github.com/JayBkr) ([#7303](https://github.com/paperless-ngx/paperless-ngx/pull/7303))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7296](https://github.com/paperless-ngx/paperless-ngx/pull/7296))
|
||||
- Chore(deps): Bump tika-client from 0.5.0 to 0.6.0 in the small-changes group [@dependabot](https://github.com/dependabot) ([#7297](https://github.com/paperless-ngx/paperless-ngx/pull/7297))
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>5 changes</summary>
|
||||
|
||||
- Change: more clearly handle init permissions error [@shamoon](https://github.com/shamoon) ([#7334](https://github.com/paperless-ngx/paperless-ngx/pull/7334))
|
||||
- Chore: add permissions info link from webUI [@shamoon](https://github.com/shamoon) ([#7310](https://github.com/paperless-ngx/paperless-ngx/pull/7310))
|
||||
- Fix: increase search input text contrast with light custom theme colors [@JayBkr](https://github.com/JayBkr) ([#7303](https://github.com/paperless-ngx/paperless-ngx/pull/7303))
|
||||
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7296](https://github.com/paperless-ngx/paperless-ngx/pull/7296))
|
||||
- Chore(deps): Bump tika-client from 0.5.0 to 0.6.0 in the small-changes group [@dependabot](https://github.com/dependabot) ([#7297](https://github.com/paperless-ngx/paperless-ngx/pull/7297))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.11.1
|
||||
|
||||
### Features
|
||||
|
||||
- Enhancement: include owner username in post-consumption variables [@Freddy-0](https://github.com/Freddy-0) ([#7270](https://github.com/paperless-ngx/paperless-ngx/pull/7270))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: support multiple inbox tags from stats widget [@shamoon](https://github.com/shamoon) ([#7281](https://github.com/paperless-ngx/paperless-ngx/pull/7281))
|
||||
- Fix: Removes Turkish from the NLTK languages [@stumpylog](https://github.com/stumpylog) ([#7246](https://github.com/paperless-ngx/paperless-ngx/pull/7246))
|
||||
- Fix: include trashed docs in existing doc check [@shamoon](https://github.com/shamoon) ([#7229](https://github.com/paperless-ngx/paperless-ngx/pull/7229))
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7261](https://github.com/paperless-ngx/paperless-ngx/pull/7261))
|
||||
- Chore(deps): Bump the small-changes group across 1 directory with 2 updates [@dependabot](https://github.com/dependabot) ([#7266](https://github.com/paperless-ngx/paperless-ngx/pull/7266))
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>7 changes</summary>
|
||||
|
||||
- Fix: support multiple inbox tags from stats widget [@shamoon](https://github.com/shamoon) ([#7281](https://github.com/paperless-ngx/paperless-ngx/pull/7281))
|
||||
- Chore(deps-dev): Bump the development group with 2 updates [@dependabot](https://github.com/dependabot) ([#7261](https://github.com/paperless-ngx/paperless-ngx/pull/7261))
|
||||
- Chore(deps): Bump the small-changes group across 1 directory with 2 updates [@dependabot](https://github.com/dependabot) ([#7266](https://github.com/paperless-ngx/paperless-ngx/pull/7266))
|
||||
- Enhancement: include owner username in post-consumption variables [@Freddy-0](https://github.com/Freddy-0) ([#7270](https://github.com/paperless-ngx/paperless-ngx/pull/7270))
|
||||
- Chore: Squash older automatic migrations [@stumpylog](https://github.com/stumpylog) ([#7267](https://github.com/paperless-ngx/paperless-ngx/pull/7267))
|
||||
- Fix: Removes Turkish from the NLTK languages [@stumpylog](https://github.com/stumpylog) ([#7246](https://github.com/paperless-ngx/paperless-ngx/pull/7246))
|
||||
- Fix: include trashed docs in existing doc check [@shamoon](https://github.com/shamoon) ([#7229](https://github.com/paperless-ngx/paperless-ngx/pull/7229))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.11.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Feature: Upgrade Gotenberg to v8 [@stumpylog](https://github.com/stumpylog) ([#7094](https://github.com/paperless-ngx/paperless-ngx/pull/7094))
|
||||
|
||||
### Features
|
||||
|
||||
- Enhancement: disable add split button when appropriate [@shamoon](https://github.com/shamoon) ([#7215](https://github.com/paperless-ngx/paperless-ngx/pull/7215))
|
||||
- Enhancement: wrapping of saved view fields d-n-d UI [@shamoon](https://github.com/shamoon) ([#7216](https://github.com/paperless-ngx/paperless-ngx/pull/7216))
|
||||
- Enhancement: support custom field icontains filter for select type [@shamoon](https://github.com/shamoon) ([#7199](https://github.com/paperless-ngx/paperless-ngx/pull/7199))
|
||||
- Feature: select custom field type [@shamoon](https://github.com/shamoon) ([#7167](https://github.com/paperless-ngx/paperless-ngx/pull/7167))
|
||||
- Feature: automatic sso redirect [@shamoon](https://github.com/shamoon) ([#7168](https://github.com/paperless-ngx/paperless-ngx/pull/7168))
|
||||
- Enhancement: show more columns in mail frontend admin [@shamoon](https://github.com/shamoon) ([#7158](https://github.com/paperless-ngx/paperless-ngx/pull/7158))
|
||||
- Enhancement: use request user as owner of split / merge docs [@shamoon](https://github.com/shamoon) ([#7112](https://github.com/paperless-ngx/paperless-ngx/pull/7112))
|
||||
- Enhancement: improve date parsing with accented characters [@fdubuy](https://github.com/fdubuy) ([#7100](https://github.com/paperless-ngx/paperless-ngx/pull/7100))
|
||||
- Feature: improve history display of object names etc [@shamoon](https://github.com/shamoon) ([#7102](https://github.com/paperless-ngx/paperless-ngx/pull/7102))
|
||||
- Feature: Upgrade Gotenberg to v8 [@stumpylog](https://github.com/stumpylog) ([#7094](https://github.com/paperless-ngx/paperless-ngx/pull/7094))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: include documents in trash for existing asn check [@shamoon](https://github.com/shamoon) ([#7189](https://github.com/paperless-ngx/paperless-ngx/pull/7189))
|
||||
- Fix: include documents in trash in sanity check [@shamoon](https://github.com/shamoon) ([#7133](https://github.com/paperless-ngx/paperless-ngx/pull/7133))
|
||||
- Fix: handle errors for trash actions and only show documents user can restore or delete [@shamoon](https://github.com/shamoon) ([#7119](https://github.com/paperless-ngx/paperless-ngx/pull/7119))
|
||||
- Fix: dont include documents in trash in counts [@shamoon](https://github.com/shamoon) ([#7111](https://github.com/paperless-ngx/paperless-ngx/pull/7111))
|
||||
- Fix: use temp dir for split / merge [@shamoon](https://github.com/shamoon) ([#7105](https://github.com/paperless-ngx/paperless-ngx/pull/7105))
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Chore: upgrade to DRF 3.15 [@shamoon](https://github.com/shamoon) ([#7134](https://github.com/paperless-ngx/paperless-ngx/pull/7134))
|
||||
- Chore(deps): Bump docker/build-push-action from 5 to 6 in the actions group [@dependabot](https://github.com/dependabot) ([#7125](https://github.com/paperless-ngx/paperless-ngx/pull/7125))
|
||||
- Chore: Ignores DRF 3.15.2 [@stumpylog](https://github.com/stumpylog) ([#7122](https://github.com/paperless-ngx/paperless-ngx/pull/7122))
|
||||
- Chore: show docker tag in UI for ci test builds [@shamoon](https://github.com/shamoon) ([#7083](https://github.com/paperless-ngx/paperless-ngx/pull/7083))
|
||||
|
||||
### Dependencies
|
||||
|
||||
<details>
|
||||
<summary>11 changes</summary>
|
||||
|
||||
- Chore: Bulk backend updates [@stumpylog](https://github.com/stumpylog) ([#7209](https://github.com/paperless-ngx/paperless-ngx/pull/7209))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 14 updates [@dependabot](https://github.com/dependabot) ([#7200](https://github.com/paperless-ngx/paperless-ngx/pull/7200))
|
||||
- Chore(deps): Bump certifi from 2024.6.2 to 2024.7.4 [@dependabot](https://github.com/dependabot) ([#7166](https://github.com/paperless-ngx/paperless-ngx/pull/7166))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 6 updates [@dependabot](https://github.com/dependabot) ([#7148](https://github.com/paperless-ngx/paperless-ngx/pull/7148))
|
||||
- Chore(deps): Bump django-multiselectfield from 0.1.12 to 0.1.13 in the django group [@dependabot](https://github.com/dependabot) ([#7147](https://github.com/paperless-ngx/paperless-ngx/pull/7147))
|
||||
- Chore(deps): Bump docker/build-push-action from 5 to 6 in the actions group [@dependabot](https://github.com/dependabot) ([#7125](https://github.com/paperless-ngx/paperless-ngx/pull/7125))
|
||||
- Chore(deps): Bump the small-changes group across 1 directory with 4 updates [@dependabot](https://github.com/dependabot) ([#7128](https://github.com/paperless-ngx/paperless-ngx/pull/7128))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 16 updates [@dependabot](https://github.com/dependabot) ([#7126](https://github.com/paperless-ngx/paperless-ngx/pull/7126))
|
||||
- Chore(deps-dev): Bump ruff from 0.4.9 to 0.5.0 in the development group across 1 directory [@dependabot](https://github.com/dependabot) ([#7120](https://github.com/paperless-ngx/paperless-ngx/pull/7120))
|
||||
- Chore(deps-dev): Bump ws from 8.17.0 to 8.17.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#7114](https://github.com/paperless-ngx/paperless-ngx/pull/7114))
|
||||
- Chore: update to Angular v18 [@shamoon](https://github.com/shamoon) ([#7106](https://github.com/paperless-ngx/paperless-ngx/pull/7106))
|
||||
</details>
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>25 changes</summary>
|
||||
|
||||
- Enhancement: disable add split button when appropriate [@shamoon](https://github.com/shamoon) ([#7215](https://github.com/paperless-ngx/paperless-ngx/pull/7215))
|
||||
- Enhancement: wrapping of saved view fields d-n-d UI [@shamoon](https://github.com/shamoon) ([#7216](https://github.com/paperless-ngx/paperless-ngx/pull/7216))
|
||||
- Chore: Bulk backend updates [@stumpylog](https://github.com/stumpylog) ([#7209](https://github.com/paperless-ngx/paperless-ngx/pull/7209))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 14 updates [@dependabot](https://github.com/dependabot) ([#7200](https://github.com/paperless-ngx/paperless-ngx/pull/7200))
|
||||
- Enhancement: support custom field icontains filter for select type [@shamoon](https://github.com/shamoon) ([#7199](https://github.com/paperless-ngx/paperless-ngx/pull/7199))
|
||||
- Chore: upgrade to DRF 3.15 [@shamoon](https://github.com/shamoon) ([#7134](https://github.com/paperless-ngx/paperless-ngx/pull/7134))
|
||||
- Feature: select custom field type [@shamoon](https://github.com/shamoon) ([#7167](https://github.com/paperless-ngx/paperless-ngx/pull/7167))
|
||||
- Feature: automatic sso redirect [@shamoon](https://github.com/shamoon) ([#7168](https://github.com/paperless-ngx/paperless-ngx/pull/7168))
|
||||
- Fix: include documents in trash for existing asn check [@shamoon](https://github.com/shamoon) ([#7189](https://github.com/paperless-ngx/paperless-ngx/pull/7189))
|
||||
- Chore: Initial conversion to pytest fixtures [@stumpylog](https://github.com/stumpylog) ([#7110](https://github.com/paperless-ngx/paperless-ngx/pull/7110))
|
||||
- Enhancement: show more columns in mail frontend admin [@shamoon](https://github.com/shamoon) ([#7158](https://github.com/paperless-ngx/paperless-ngx/pull/7158))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 6 updates [@dependabot](https://github.com/dependabot) ([#7148](https://github.com/paperless-ngx/paperless-ngx/pull/7148))
|
||||
- Chore(deps): Bump django-multiselectfield from 0.1.12 to 0.1.13 in the django group [@dependabot](https://github.com/dependabot) ([#7147](https://github.com/paperless-ngx/paperless-ngx/pull/7147))
|
||||
- Fix: include documents in trash in sanity check [@shamoon](https://github.com/shamoon) ([#7133](https://github.com/paperless-ngx/paperless-ngx/pull/7133))
|
||||
- Chore(deps): Bump the small-changes group across 1 directory with 4 updates [@dependabot](https://github.com/dependabot) ([#7128](https://github.com/paperless-ngx/paperless-ngx/pull/7128))
|
||||
- Chore(deps): Bump the frontend-angular-dependencies group in /src-ui with 16 updates [@dependabot](https://github.com/dependabot) ([#7126](https://github.com/paperless-ngx/paperless-ngx/pull/7126))
|
||||
- Enhancement: use request user as owner of split / merge docs [@shamoon](https://github.com/shamoon) ([#7112](https://github.com/paperless-ngx/paperless-ngx/pull/7112))
|
||||
- Fix: handle errors for trash actions and only show documents user can restore or delete [@shamoon](https://github.com/shamoon) ([#7119](https://github.com/paperless-ngx/paperless-ngx/pull/7119))
|
||||
- Chore(deps-dev): Bump ruff from 0.4.9 to 0.5.0 in the development group across 1 directory [@dependabot](https://github.com/dependabot) ([#7120](https://github.com/paperless-ngx/paperless-ngx/pull/7120))
|
||||
- Chore(deps-dev): Bump ws from 8.17.0 to 8.17.1 in /src-ui [@dependabot](https://github.com/dependabot) ([#7114](https://github.com/paperless-ngx/paperless-ngx/pull/7114))
|
||||
- Chore: update to Angular v18 [@shamoon](https://github.com/shamoon) ([#7106](https://github.com/paperless-ngx/paperless-ngx/pull/7106))
|
||||
- Enhancement: improve date parsing with accented characters [@fdubuy](https://github.com/fdubuy) ([#7100](https://github.com/paperless-ngx/paperless-ngx/pull/7100))
|
||||
- Feature: improve history display of object names etc [@shamoon](https://github.com/shamoon) ([#7102](https://github.com/paperless-ngx/paperless-ngx/pull/7102))
|
||||
- Fix: dont include documents in trash in counts [@shamoon](https://github.com/shamoon) ([#7111](https://github.com/paperless-ngx/paperless-ngx/pull/7111))
|
||||
- Fix: use temp dir for split / merge [@shamoon](https://github.com/shamoon) ([#7105](https://github.com/paperless-ngx/paperless-ngx/pull/7105))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.10.2
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix: always update document modified property on bulk edit operations [@shamoon](https://github.com/shamoon) ([#7079](https://github.com/paperless-ngx/paperless-ngx/pull/7079))
|
||||
- Fix: correct frontend retrieval of trash delay setting [@shamoon](https://github.com/shamoon) ([#7067](https://github.com/paperless-ngx/paperless-ngx/pull/7067))
|
||||
- Fix: index fresh document data after update archive file [@shamoon](https://github.com/shamoon) ([#7057](https://github.com/paperless-ngx/paperless-ngx/pull/7057))
|
||||
- Fix: Safari browser PDF viewer not loading in 2.10.x [@shamoon](https://github.com/shamoon) ([#7056](https://github.com/paperless-ngx/paperless-ngx/pull/7056))
|
||||
- Fix: Prefer the exporter metadata JSON file over the version JSON file [@stumpylog](https://github.com/stumpylog) ([#7048](https://github.com/paperless-ngx/paperless-ngx/pull/7048))
|
||||
|
||||
### All App Changes
|
||||
|
||||
<details>
|
||||
<summary>5 changes</summary>
|
||||
|
||||
- Fix: always update document modified property on bulk edit operations [@shamoon](https://github.com/shamoon) ([#7079](https://github.com/paperless-ngx/paperless-ngx/pull/7079))
|
||||
- Fix: correct frontend retrieval of trash delay setting [@shamoon](https://github.com/shamoon) ([#7067](https://github.com/paperless-ngx/paperless-ngx/pull/7067))
|
||||
- Fix: index fresh document data after update archive file [@shamoon](https://github.com/shamoon) ([#7057](https://github.com/paperless-ngx/paperless-ngx/pull/7057))
|
||||
- Fix: Safari browser PDF viewer not loading in 2.10.x [@shamoon](https://github.com/shamoon) ([#7056](https://github.com/paperless-ngx/paperless-ngx/pull/7056))
|
||||
- Fix: Prefer the exporter metadata JSON file over the version JSON file [@stumpylog](https://github.com/stumpylog) ([#7048](https://github.com/paperless-ngx/paperless-ngx/pull/7048))
|
||||
</details>
|
||||
|
||||
## paperless-ngx 2.10.1
|
||||
|
||||
### Bug Fixes
|
||||
|
@@ -594,7 +594,15 @@ system. See the corresponding
|
||||
|
||||
#### [`PAPERLESS_DISABLE_REGULAR_LOGIN=<bool>`](#PAPERLESS_DISABLE_REGULAR_LOGIN) {#PAPERLESS_DISABLE_REGULAR_LOGIN}
|
||||
|
||||
: Disables the regular frontend username / password login, i.e. once you have setup SSO. Note that this setting does not disable the Django admin login. To prevent logins directly to Django, consider blocking `/admin/` in your [web server or reverse proxy configuration](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx).
|
||||
: Disables the regular frontend username / password login, i.e. once you have setup SSO. Note that this setting does not disable the Django admin login nor logging in with local credentials via the API. To prevent access to the Django admin, consider blocking `/admin/` in your [web server or reverse proxy configuration](https://github.com/paperless-ngx/paperless-ngx/wiki/Using-a-Reverse-Proxy-with-Paperless-ngx).
|
||||
|
||||
You can optionally also automatically redirect users to the SSO login with [PAPERLESS_REDIRECT_LOGIN_TO_SSO](#PAPERLESS_REDIRECT_LOGIN_TO_SSO)
|
||||
|
||||
Defaults to False
|
||||
|
||||
#### [`PAPERLESS_REDIRECT_LOGIN_TO_SSO=<bool>`](#PAPERLESS_REDIRECT_LOGIN_TO_SSO) {#PAPERLESS_REDIRECT_LOGIN_TO_SSO}
|
||||
|
||||
: When this setting is enabled users will automatically be redirected (using javascript) to the first SSO provider login. You may still want to disable the frontend login form for clarity.
|
||||
|
||||
Defaults to False
|
||||
|
||||
@@ -1141,6 +1149,18 @@ within your documents.
|
||||
second, and year last order. Characters D, M, or Y can be shuffled
|
||||
to meet the required order.
|
||||
|
||||
#### [`PAPERLESS_GPG_DECRYPTOR=<bool>`](#PAPERLESS_GPG_DECRYPTOR) {#PAPERLESS_GPG_DECRYPTOR}
|
||||
|
||||
: Enable or disable the GPG decryptor for encrypted emails. See [GPG Decryptor](advanced_usage.md#gpg-decryptor) for more information.
|
||||
|
||||
Defaults to false.
|
||||
|
||||
#### [`PAPERLESS_EMAIL_GNUPG_HOME=<str>`](#PAPERLESS_EMAIL_GNUPG_HOME) {#PAPERLESS_EMAIL_GNUPG_HOME}
|
||||
|
||||
: Optional, sets the `GNUPG_HOME` path to use with GPG decryptor for encrypted emails. See [GPG Decryptor](advanced_usage.md#gpg-decryptor) for more information. If not set, defaults to the default `GNUPG_HOME` path.
|
||||
|
||||
Defaults to <not set>.
|
||||
|
||||
### Polling {#polling}
|
||||
|
||||
#### [`PAPERLESS_CONSUMER_POLLING=<num>`](#PAPERLESS_CONSUMER_POLLING) {#PAPERLESS_CONSUMER_POLLING}
|
||||
@@ -1269,6 +1289,15 @@ combination with PAPERLESS_CONSUMER_BARCODE_UPSCALE bigger than 1.0.
|
||||
|
||||
Defaults to "300"
|
||||
|
||||
#### [`PAPERLESS_CONSUMER_BARCODE_MAX_PAGES=<int>`](#PAPERLESS_CONSUMER_BARCODE_MAX_PAGES) {#PAPERLESS_CONSUMER_BARCODE_MAX_PAGES}
|
||||
|
||||
: Because barcode detection is a computationally-intensive operation, this setting
|
||||
limits the detection of barcodes to a number of first pages. If your scanner has
|
||||
a limit for the number of pages that can be scanned it would be sensible to set this
|
||||
as the limit here.
|
||||
|
||||
Defaults to "0", allowing all pages to be checked for barcodes.
|
||||
|
||||
#### [`PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE=<bool>`](#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE) {#PAPERLESS_CONSUMER_ENABLE_TAG_BARCODE}
|
||||
|
||||
: Enables the detection of barcodes in the scanned document and
|
||||
|
@@ -540,8 +540,7 @@ are released, dependency support is confirmed, etc.
|
||||
15. Optional: If using the NLTK machine learning processing (see
|
||||
[`PAPERLESS_ENABLE_NLTK`](configuration.md#PAPERLESS_ENABLE_NLTK) for details),
|
||||
download the NLTK data for the Snowball
|
||||
Stemmer, Stopwords and Punkt tokenizer to your
|
||||
`PAPERLESS_DATA_DIR/nltk`. Refer to the [NLTK
|
||||
Stemmer, Stopwords and Punkt tokenizer to `/usr/share/nltk_data`. Refer to the [NLTK
|
||||
instructions](https://www.nltk.org/data.html) for details on how to
|
||||
download the data.
|
||||
|
||||
|
@@ -136,7 +136,8 @@ These rules perform the following:
|
||||
|
||||
Paperless will check all emails only once and completely ignore messages
|
||||
that do not match your filters. It will also only perform the rule action
|
||||
on e-mails that it has consumed documents from.
|
||||
on e-mails that it has consumed documents from. The filename attachment
|
||||
patterns can include wildcards and multiple patterns separated by a comma.
|
||||
|
||||
The actions all ensure that the same mail is not consumed twice by
|
||||
different means. These are as follows:
|
||||
@@ -237,9 +238,13 @@ Settings > Users & Groups, assuming the user has access. If a user is designated
|
||||
as a member of a group those permissions will be inherited and this is reflected in the UI. Explicit
|
||||
permissions can be granted to limit access to certain parts of the UI (and corresponding API endpoints).
|
||||
|
||||
!!! note
|
||||
!!! tip
|
||||
|
||||
Superusers can access all parts of the front and backend application as well as any and all objects.
|
||||
By default, new users are not granted any permissions, except those inherited from any group(s) of which they are a member.
|
||||
|
||||
#### Superusers
|
||||
|
||||
Superusers can access all parts of the front and backend application as well as any and all objects.
|
||||
|
||||
#### Admin Status
|
||||
|
||||
@@ -248,29 +253,29 @@ as well as accessing the Django backend.
|
||||
|
||||
#### Detailed Explanation of Global Permissions {#global-permissions}
|
||||
|
||||
Global permissions define what areas of the app and API endpoints the user can access. For example, they
|
||||
Global permissions define what areas of the app and API endpoints users can access. For example, they
|
||||
determine if a user can create, edit, delete or view _any_ documents, but individual documents themselves
|
||||
still have "object-level" permissions.
|
||||
|
||||
| Type | Details |
|
||||
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| AppConfig | _Change_ or higher permissions grants access to the "Application Configuration" area. |
|
||||
| Correspondent | Grants global permissions to add, edit, delete or view Correspondents. |
|
||||
| CustomField | Grants global permissions to add, edit, delete or view Custom Fields. |
|
||||
| Document | Grants global permissions to add, edit, delete or view Documents. |
|
||||
| DocumentType | Grants global permissions to add, edit, delete or view Document Types. |
|
||||
| Group | Grants global permissions to add, edit, delete or view Groups. |
|
||||
| MailAccount | Grants global permissions to add, edit, delete or view Mail Accounts. |
|
||||
| MailRule | Grants global permissions to add, edit, delete or view Mail Rules. |
|
||||
| Note | Grants global permissions to add, edit, delete or view Notes. |
|
||||
| PaperlessTask | Grants global permissions to view or dismiss (_Change_) File Tasks. |
|
||||
| SavedView | Grants global permissions to add, edit, delete or view Saved Views. |
|
||||
| ShareLink | Grants global permissions to add, delete or view Share Links. |
|
||||
| StoragePath | Grants global permissions to add, edit, delete or view Storage Paths. |
|
||||
| Tag | Grants global permissions to add, edit, delete or view Tags. |
|
||||
| UISettings | Grants global permissions to add, edit, delete or view the UI settings that are used by the web app.<br/>Users expected to access the web UI should usually be granted at least _View_ permissions. |
|
||||
| User | Grants global permissions to add, edit, delete or view Users. |
|
||||
| Workflow | Grants global permissions to add, edit, delete or view Workflows.<br/>Note that Workflows are global, in other words all users who can access workflows have access to the same set of them. |
|
||||
| Type | Details |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| AppConfig | _Change_ or higher permissions grants access to the "Application Configuration" area. |
|
||||
| Correspondent | Add, edit, delete or view Correspondents. |
|
||||
| CustomField | Add, edit, delete or view Custom Fields. |
|
||||
| Document | Add, edit, delete or view Documents. |
|
||||
| DocumentType | Add, edit, delete or view Document Types. |
|
||||
| Group | Add, edit, delete or view Groups. |
|
||||
| MailAccount | Add, edit, delete or view Mail Accounts. |
|
||||
| MailRule | Add, edit, delete or view Mail Rules. |
|
||||
| Note | Add, edit, delete or view Notes. |
|
||||
| PaperlessTask | View or dismiss (_Change_) File Tasks. |
|
||||
| SavedView | Add, edit, delete or view Saved Views. |
|
||||
| ShareLink | Add, delete or view Share Links. |
|
||||
| StoragePath | Add, edit, delete or view Storage Paths. |
|
||||
| Tag | Add, edit, delete or view Tags. |
|
||||
| UISettings | Add, edit, delete or view the UI settings that are used by the web app.<br/>:warning: **Users that will access the web UI must be granted at least _View_ permissions.** |
|
||||
| User | Add, edit, delete or view Users. |
|
||||
| Workflow | Add, edit, delete or view Workflows.<br/>Note that Workflows are global, in other words all users who can access workflows have access to the same set of them. |
|
||||
|
||||
#### Detailed Explanation of Object Permissions {#object-permissions}
|
||||
|
||||
@@ -445,6 +450,7 @@ The following custom field types are supported:
|
||||
- `Number`: float number e.g. 12.3456
|
||||
- `Monetary`: [ISO 4217 currency code](https://en.wikipedia.org/wiki/ISO_4217#List_of_ISO_4217_currency_codes) and a number with exactly two decimals, e.g. USD12.30
|
||||
- `Document Link`: reference(s) to other document(s) displayed as links, automatically creates a symmetrical link in reverse
|
||||
- `Select`: a pre-defined list of strings from which the user can choose
|
||||
|
||||
## Share Links
|
||||
|
||||
|
11
mkdocs.yml
11
mkdocs.yml
@@ -6,6 +6,12 @@ theme:
|
||||
text: Roboto
|
||||
code: Roboto Mono
|
||||
palette:
|
||||
# Palette toggle for automatic mode
|
||||
- media: "(prefers-color-scheme)"
|
||||
toggle:
|
||||
icon: material/brightness-auto
|
||||
name: Switch to light mode
|
||||
|
||||
# Palette toggle for light mode
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
@@ -18,7 +24,7 @@ theme:
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: Switch to light mode
|
||||
name: Switch to system preference
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.top
|
||||
@@ -49,6 +55,9 @@ markdown_extensions:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
strict: true
|
||||
nav:
|
||||
- index.md
|
||||
|
@@ -14,6 +14,7 @@ following additional information about it:
|
||||
* Thumbnail Path: ${DOCUMENT_THUMBNAIL_PATH}
|
||||
* Download URL: ${DOCUMENT_DOWNLOAD_URL}
|
||||
* Thumbnail URL: ${DOCUMENT_THUMBNAIL_URL}
|
||||
* Owner Name: ${DOCUMENT_OWNER}
|
||||
* Correspondent: ${DOCUMENT_CORRESPONDENT}
|
||||
* Tags: ${DOCUMENT_TAGS}
|
||||
|
||||
|
@@ -33,6 +33,7 @@
|
||||
"it-IT": "src/locale/messages.it_IT.xlf",
|
||||
"ja-JP": "src/locale/messages.ja_JP.xlf",
|
||||
"lb-LU": "src/locale/messages.lb_LU.xlf",
|
||||
"ko-KR": "src/locale/messages.ko_KR.xlf",
|
||||
"nl-NL": "src/locale/messages.nl_NL.xlf",
|
||||
"no-NO": "src/locale/messages.no_NO.xlf",
|
||||
"pl-PL": "src/locale/messages.pl_PL.xlf",
|
||||
@@ -66,8 +67,8 @@
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest",
|
||||
{
|
||||
"glob": "{pdf.worker.min.js,pdf.min.js}",
|
||||
"input": "node_modules/pdfjs-dist/build/",
|
||||
"glob": "{pdf.worker.min.mjs,pdf.min.mjs}",
|
||||
"input": "node_modules/pdfjs-dist/legacy/build/",
|
||||
"output": "/assets/js/"
|
||||
}
|
||||
],
|
||||
@@ -77,7 +78,6 @@
|
||||
"scripts": [],
|
||||
"allowedCommonJsDependencies": [
|
||||
"ng2-pdf-viewer",
|
||||
"filesize",
|
||||
"file-saver"
|
||||
],
|
||||
"vendorChunk": true,
|
||||
|
@@ -9,7 +9,7 @@ test('dashboard inbox link', async ({ page }) => {
|
||||
await page.routeFromHAR(REQUESTS_HAR1, { notFound: 'fallback' })
|
||||
await page.goto('/dashboard')
|
||||
await page.getByRole('link', { name: 'Documents in inbox' }).click()
|
||||
await expect(page).toHaveURL(/tags__id__all=9/)
|
||||
await expect(page).toHaveURL(/tags__id__in=9/)
|
||||
await expect(page.locator('pngx-document-list')).toHaveText(/8 documents/)
|
||||
})
|
||||
|
||||
|
@@ -236,7 +236,7 @@
|
||||
"content": {
|
||||
"size": -1,
|
||||
"mimeType": "application/json",
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tag\":9,\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tags\":[9],\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
},
|
||||
"headersSize": -1,
|
||||
"bodySize": -1,
|
||||
@@ -250,7 +250,7 @@
|
||||
"time": 0.609,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=10&ordering=-created&truncate_content=true&tags__id__all=9",
|
||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=10&ordering=-created&truncate_content=true&tags__id__in=9",
|
||||
"httpVersion": "HTTP/1.1",
|
||||
"cookies": [],
|
||||
"headers": [
|
||||
@@ -284,7 +284,7 @@
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tags__id__all",
|
||||
"name": "tags__id__in",
|
||||
"value": "9"
|
||||
}
|
||||
],
|
||||
@@ -468,7 +468,7 @@
|
||||
"time": 0.951,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__all=9",
|
||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=50&ordering=-created&truncate_content=true&tags__id__in=9",
|
||||
"httpVersion": "HTTP/1.1",
|
||||
"cookies": [],
|
||||
"headers": [
|
||||
@@ -502,7 +502,7 @@
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tags__id__all",
|
||||
"name": "tags__id__in",
|
||||
"value": "9"
|
||||
}
|
||||
],
|
||||
|
@@ -236,7 +236,7 @@
|
||||
"content": {
|
||||
"size": -1,
|
||||
"mimeType": "application/json",
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tag\":9,\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tags\":[9],\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
},
|
||||
"headersSize": -1,
|
||||
"bodySize": -1,
|
||||
@@ -250,7 +250,7 @@
|
||||
"time": 0.622,
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=10&ordering=-created&truncate_content=true&tags__id__all=9",
|
||||
"url": "http://localhost:8000/api/documents/?page=1&page_size=10&ordering=-created&truncate_content=true&tags__id__in=9",
|
||||
"httpVersion": "HTTP/1.1",
|
||||
"cookies": [],
|
||||
"headers": [
|
||||
@@ -284,7 +284,7 @@
|
||||
"value": "true"
|
||||
},
|
||||
{
|
||||
"name": "tags__id__all",
|
||||
"name": "tags__id__in",
|
||||
"value": "9"
|
||||
}
|
||||
],
|
||||
|
@@ -236,7 +236,7 @@
|
||||
"content": {
|
||||
"size": -1,
|
||||
"mimeType": "application/json",
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tag\":9,\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tags\":[9],\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
},
|
||||
"headersSize": -1,
|
||||
"bodySize": -1,
|
||||
|
@@ -236,7 +236,7 @@
|
||||
"content": {
|
||||
"size": -1,
|
||||
"mimeType": "application/json",
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tag\":9,\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
"text": "{\"documents_total\":61,\"documents_inbox\":8,\"inbox_tags\":[9],\"document_file_type_counts\":[{\"mime_type\":\"application/pdf\",\"mime_type_count\":57},{\"mime_type\":\"text/plain\",\"mime_type_count\":3},{\"mime_type\":\"text/csv\",\"mime_type_count\":1}],\"character_count\":2407053}"
|
||||
},
|
||||
"headersSize": -1,
|
||||
"bodySize": -1,
|
||||
|
@@ -84,13 +84,8 @@ test('date filtering', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Dates' }).click()
|
||||
await page.getByRole('menuitem', { name: 'Last 3 months' }).first().click()
|
||||
await expect(page.locator('pngx-document-list')).toHaveText(/one document/i)
|
||||
await page.getByRole('button', { name: 'Dates Clear selected' }).click()
|
||||
await page.getByRole('button', { name: 'Dates' }).click()
|
||||
await page
|
||||
.getByRole('menuitem', { name: 'After mm/dd/yyyy' })
|
||||
.getByRole('button')
|
||||
.first()
|
||||
.click()
|
||||
await page.getByRole('menuitem', { name: 'Last 3 months' }).first().click()
|
||||
await page.getByLabel('Datesselected').getByRole('button').first().click()
|
||||
await page.getByRole('combobox', { name: 'Select month' }).selectOption('12')
|
||||
await page.getByRole('combobox', { name: 'Select year' }).selectOption('2022')
|
||||
await page.getByText('11', { exact: true }).click()
|
||||
|
File diff suppressed because it is too large
Load Diff
7690
src-ui/package-lock.json
generated
7690
src-ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,58 +11,58 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^17.3.10",
|
||||
"@angular/common": "~17.3.9",
|
||||
"@angular/compiler": "~17.3.9",
|
||||
"@angular/core": "~17.3.9",
|
||||
"@angular/forms": "~17.3.9",
|
||||
"@angular/localize": "~17.3.9",
|
||||
"@angular/platform-browser": "~17.3.9",
|
||||
"@angular/platform-browser-dynamic": "~17.3.9",
|
||||
"@angular/router": "~17.3.9",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@ng-select/ng-select": "^12.0.7",
|
||||
"@angular/cdk": "^18.2.2",
|
||||
"@angular/common": "~18.2.2",
|
||||
"@angular/compiler": "~18.2.2",
|
||||
"@angular/core": "~18.2.2",
|
||||
"@angular/forms": "~18.2.2",
|
||||
"@angular/localize": "~18.2.2",
|
||||
"@angular/platform-browser": "~18.2.2",
|
||||
"@angular/platform-browser-dynamic": "~18.2.2",
|
||||
"@angular/router": "~18.2.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "^17.0.1",
|
||||
"@ng-select/ng-select": "^13.7.0",
|
||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"file-saver": "^2.0.5",
|
||||
"mime-names": "^1.0.0",
|
||||
"ng2-pdf-viewer": "^10.2.2",
|
||||
"ng2-pdf-viewer": "^10.3.0",
|
||||
"ngx-bootstrap-icons": "^1.9.3",
|
||||
"ngx-color": "^9.0.0",
|
||||
"ngx-cookie-service": "^17.1.0",
|
||||
"ngx-cookie-service": "^18.0.0",
|
||||
"ngx-file-drop": "^16.0.0",
|
||||
"ngx-filesize": "^3.0.3",
|
||||
"ngx-ui-tour-ng-bootstrap": "^14.0.3",
|
||||
"ngx-ui-tour-ng-bootstrap": "^15.0.0",
|
||||
"rxjs": "^7.8.1",
|
||||
"tslib": "^2.6.2",
|
||||
"uuid": "^9.0.1",
|
||||
"zone.js": "^0.14.4"
|
||||
"tslib": "^2.7.0",
|
||||
"uuid": "^10.0.0",
|
||||
"zone.js": "^0.14.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/jest": "17.0.3",
|
||||
"@angular-devkit/build-angular": "~17.3.7",
|
||||
"@angular-eslint/builder": "17.4.1",
|
||||
"@angular-eslint/eslint-plugin": "17.4.1",
|
||||
"@angular-eslint/eslint-plugin-template": "17.4.1",
|
||||
"@angular-eslint/schematics": "17.4.1",
|
||||
"@angular-eslint/template-parser": "17.4.1",
|
||||
"@angular/cli": "~17.3.7",
|
||||
"@angular/compiler-cli": "~17.3.2",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@angular-builders/jest": "^18.0.0",
|
||||
"@angular-devkit/build-angular": "^18.2.2",
|
||||
"@angular-devkit/core": "^18.2.2",
|
||||
"@angular-devkit/schematics": "^18.2.2",
|
||||
"@angular-eslint/builder": "18.3.0",
|
||||
"@angular-eslint/eslint-plugin": "18.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "18.3.0",
|
||||
"@angular-eslint/schematics": "18.3.0",
|
||||
"@angular-eslint/template-parser": "18.3.0",
|
||||
"@angular/cli": "~18.2.2",
|
||||
"@angular/compiler-cli": "~18.2.2",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.12.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||
"@typescript-eslint/parser": "^7.4.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.57.0",
|
||||
"@types/node": "^22.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"@typescript-eslint/utils": "^8.0.0",
|
||||
"eslint": "^9.9.1",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-preset-angular": "^14.1.0",
|
||||
"jest-preset-angular": "^14.2.2",
|
||||
"jest-websocket-mock": "^2.5.0",
|
||||
"patch-package": "^8.0.0",
|
||||
"ts-node": "~10.9.1",
|
||||
"typescript": "^5.3.3",
|
||||
"wait-on": "^7.2.0"
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import localeFr from '@angular/common/locales/fr'
|
||||
import localeHu from '@angular/common/locales/hu'
|
||||
import localeIt from '@angular/common/locales/it'
|
||||
import localeJa from '@angular/common/locales/ja'
|
||||
import localeKo from '@angular/common/locales/ko'
|
||||
import localeLb from '@angular/common/locales/lb'
|
||||
import localeNl from '@angular/common/locales/nl'
|
||||
import localeNo from '@angular/common/locales/no'
|
||||
@@ -55,6 +56,7 @@ registerLocaleData(localeFr)
|
||||
registerLocaleData(localeHu)
|
||||
registerLocaleData(localeIt)
|
||||
registerLocaleData(localeJa)
|
||||
registerLocaleData(localeKo)
|
||||
registerLocaleData(localeLb)
|
||||
registerLocaleData(localeNl)
|
||||
registerLocaleData(localeNo)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
@@ -24,6 +24,7 @@ import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HotKeyService } from './services/hot-key.service'
|
||||
import { PermissionsGuard } from './guards/permissions.guard'
|
||||
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('AppComponent', () => {
|
||||
let component: AppComponent
|
||||
@@ -39,14 +40,18 @@ describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AppComponent, ToastsComponent, FileDropComponent],
|
||||
providers: [PermissionsGuard, DirtySavedViewGuard],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
TourNgBootstrapModule,
|
||||
RouterModule.forRoot(routes),
|
||||
NgxFileDropModule,
|
||||
NgbModalModule,
|
||||
],
|
||||
providers: [
|
||||
PermissionsGuard,
|
||||
DirtySavedViewGuard,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
tourService = TestBed.inject(TourService)
|
||||
|
@@ -36,7 +36,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private hotKeyService: HotKeyService
|
||||
) {
|
||||
let anyWindow = window as any
|
||||
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
|
||||
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.mjs'
|
||||
this.settings.updateAppearanceSettings()
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,11 @@ import {
|
||||
NgbDateParserFormatter,
|
||||
NgbModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'
|
||||
import {
|
||||
HTTP_INTERCEPTORS,
|
||||
provideHttpClient,
|
||||
withInterceptorsFromDi,
|
||||
} from '@angular/common/http'
|
||||
import { DocumentListComponent } from './components/document-list/document-list.component'
|
||||
import { DocumentDetailComponent } from './components/document-detail/document-detail.component'
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component'
|
||||
@@ -115,7 +119,6 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { ConfirmButtonComponent } from './components/common/confirm-button/confirm-button.component'
|
||||
import { MonetaryComponent } from './components/common/input/monetary/monetary.component'
|
||||
import { SystemStatusDialogComponent } from './components/common/system-status-dialog/system-status-dialog.component'
|
||||
import { NgxFilesizeModule } from 'ngx-filesize'
|
||||
import { RotateConfirmDialogComponent } from './components/common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
|
||||
import { MergeConfirmDialogComponent } from './components/common/confirm-dialog/merge-confirm-dialog/merge-confirm-dialog.component'
|
||||
import { SplitConfirmDialogComponent } from './components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
|
||||
@@ -344,6 +347,7 @@ import localeFr from '@angular/common/locales/fr'
|
||||
import localeHu from '@angular/common/locales/hu'
|
||||
import localeIt from '@angular/common/locales/it'
|
||||
import localeJa from '@angular/common/locales/ja'
|
||||
import localeKo from '@angular/common/locales/ko'
|
||||
import localeLb from '@angular/common/locales/lb'
|
||||
import localeNl from '@angular/common/locales/nl'
|
||||
import localeNo from '@angular/common/locales/no'
|
||||
@@ -375,6 +379,7 @@ registerLocaleData(localeFr)
|
||||
registerLocaleData(localeHu)
|
||||
registerLocaleData(localeIt)
|
||||
registerLocaleData(localeJa)
|
||||
registerLocaleData(localeKo)
|
||||
registerLocaleData(localeLb)
|
||||
registerLocaleData(localeNl)
|
||||
registerLocaleData(localeNo)
|
||||
@@ -500,11 +505,11 @@ function initializeApp(settings: SettingsService) {
|
||||
DeletePagesConfirmDialogComponent,
|
||||
TrashComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
NgbModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
PdfViewerModule,
|
||||
@@ -514,7 +519,6 @@ function initializeApp(settings: SettingsService) {
|
||||
TourNgBootstrapModule,
|
||||
DragDropModule,
|
||||
NgxBootstrapIconsModule.pick(icons),
|
||||
NgxFilesizeModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -543,7 +547,7 @@ function initializeApp(settings: SettingsService) {
|
||||
DirtyDocGuard,
|
||||
DirtySavedViewGuard,
|
||||
UsernamePipe,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
@@ -5,7 +5,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { OutputTypeConfig } from 'src/app/data/paperless-config'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
@@ -18,6 +18,7 @@ import { SelectComponent } from '../../common/input/select/select.component'
|
||||
import { FileComponent } from '../../common/input/file/file.component'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('ConfigComponent', () => {
|
||||
let component: ConfigComponent
|
||||
@@ -38,7 +39,6 @@ describe('ConfigComponent', () => {
|
||||
PageHeaderComponent,
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
BrowserModule,
|
||||
NgbModule,
|
||||
NgSelectModule,
|
||||
@@ -46,6 +46,10 @@ describe('ConfigComponent', () => {
|
||||
ReactiveFormsModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
configService = TestBed.inject(ConfigService)
|
||||
|
@@ -8,10 +8,11 @@ import { LogService } from 'src/app/services/rest/log.service'
|
||||
import { PageHeaderComponent } from '../../common/page-header/page-header.component'
|
||||
import { LogsComponent } from './logs.component'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgbModule, NgbNavLink } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { BrowserModule, By } from '@angular/platform-browser'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const paperless_logs = [
|
||||
'[2023-05-29 03:05:01,224] [DEBUG] [paperless.tasks] Training data unchanged.',
|
||||
@@ -37,13 +38,15 @@ describe('LogsComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LogsComponent, PageHeaderComponent],
|
||||
providers: [],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
BrowserModule,
|
||||
NgbModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
logService = TestBed.inject(LogService)
|
||||
|
@@ -348,7 +348,7 @@
|
||||
|
||||
@for (view of savedViews; track view) {
|
||||
<li class="list-group-item py-3">
|
||||
<div [formGroupName]="view.id" class="row">
|
||||
<div [formGroupName]="view.id">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<pngx-input-text title="Name" formControlName="name"></pngx-input-text>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ViewportScroller, DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { By } from '@angular/platform-browser'
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
} from 'src/app/data/system-status'
|
||||
import { DragDropSelectComponent } from '../../common/input/drag-drop-select/drag-drop-select.component'
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const savedViews = [
|
||||
{ id: 1, name: 'view1', show_in_sidebar: true, show_on_dashboard: true },
|
||||
@@ -100,10 +101,8 @@ describe('SettingsComponent', () => {
|
||||
ConfirmButtonComponent,
|
||||
DragDropSelectComponent,
|
||||
],
|
||||
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
|
||||
imports: [
|
||||
NgbModule,
|
||||
HttpClientTestingModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@@ -113,6 +112,13 @@ describe('SettingsComponent', () => {
|
||||
NgbModalModule,
|
||||
DragDropModule,
|
||||
],
|
||||
providers: [
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
PermissionsGuard,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
router = TestBed.inject(Router)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import {
|
||||
HttpTestingController,
|
||||
HttpClientTestingModule,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
@@ -30,6 +30,7 @@ import { TasksComponent } from './tasks.component'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const tasks: PaperlessTask[] = [
|
||||
{
|
||||
@@ -125,6 +126,12 @@ describe('TasksComponent', () => {
|
||||
CustomDatePipe,
|
||||
ConfirmDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
NgbModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
FormsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: PermissionsService,
|
||||
@@ -135,13 +142,8 @@ describe('TasksComponent', () => {
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
PermissionsGuard,
|
||||
],
|
||||
imports: [
|
||||
NgbModule,
|
||||
HttpClientTestingModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
FormsModule,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -70,11 +70,11 @@ export class TasksComponent
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
modal.close()
|
||||
this.tasksService.dismissTasks(tasks)
|
||||
this.selectedTasks.clear()
|
||||
this.clearSelection()
|
||||
})
|
||||
} else {
|
||||
this.tasksService.dismissTasks(tasks)
|
||||
this.selectedTasks.clear()
|
||||
this.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,9 +11,11 @@ import {
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { TrashService } from 'src/app/services/trash.service'
|
||||
import { of } from 'rxjs'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
|
||||
const documentsInTrash = [
|
||||
{
|
||||
@@ -35,6 +37,7 @@ describe('TrashComponent', () => {
|
||||
let fixture: ComponentFixture<TrashComponent>
|
||||
let trashService: TrashService
|
||||
let modalService: NgbModal
|
||||
let toastService: ToastService
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -42,6 +45,7 @@ describe('TrashComponent', () => {
|
||||
TrashComponent,
|
||||
PageHeaderComponent,
|
||||
ConfirmDialogComponent,
|
||||
SafeHtmlPipe,
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
@@ -56,6 +60,7 @@ describe('TrashComponent', () => {
|
||||
fixture = TestBed.createComponent(TrashComponent)
|
||||
trashService = TestBed.inject(TrashService)
|
||||
modalService = TestBed.inject(NgbModal)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
component = fixture.componentInstance
|
||||
fixture.detectChanges()
|
||||
})
|
||||
@@ -74,12 +79,20 @@ describe('TrashComponent', () => {
|
||||
expect(component.documentsInTrash).toEqual(documentsInTrash)
|
||||
})
|
||||
|
||||
it('should support delete document', () => {
|
||||
it('should support delete document, show error if needed', () => {
|
||||
const trashSpy = jest.spyOn(trashService, 'emptyTrash')
|
||||
let modal
|
||||
modalService.activeInstances.subscribe((instances) => {
|
||||
modal = instances[0]
|
||||
})
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
// fail first
|
||||
trashSpy.mockReturnValue(throwError(() => 'Error'))
|
||||
component.delete(documentsInTrash[0])
|
||||
modal.componentInstance.confirmClicked.next()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
|
||||
trashSpy.mockReturnValue(of('OK'))
|
||||
component.delete(documentsInTrash[0])
|
||||
expect(modal).toBeDefined()
|
||||
@@ -87,12 +100,20 @@ describe('TrashComponent', () => {
|
||||
expect(trashSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support empty trash', () => {
|
||||
it('should support empty trash, show error if needed', () => {
|
||||
const trashSpy = jest.spyOn(trashService, 'emptyTrash')
|
||||
let modal
|
||||
modalService.activeInstances.subscribe((instances) => {
|
||||
modal = instances[instances.length - 1]
|
||||
})
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
// fail first
|
||||
trashSpy.mockReturnValue(throwError(() => 'Error'))
|
||||
component.emptyTrash()
|
||||
modal.componentInstance.confirmClicked.next()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
|
||||
trashSpy.mockReturnValue(of('OK'))
|
||||
component.emptyTrash()
|
||||
expect(modal).toBeDefined()
|
||||
@@ -104,18 +125,34 @@ describe('TrashComponent', () => {
|
||||
expect(trashSpy).toHaveBeenCalledWith([1, 2])
|
||||
})
|
||||
|
||||
it('should support restore document', () => {
|
||||
it('should support restore document, show error if needed', () => {
|
||||
const restoreSpy = jest.spyOn(trashService, 'restoreDocuments')
|
||||
const reloadSpy = jest.spyOn(component, 'reload')
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
// fail first
|
||||
restoreSpy.mockReturnValue(throwError(() => 'Error'))
|
||||
component.restore(documentsInTrash[0])
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).not.toHaveBeenCalled()
|
||||
|
||||
restoreSpy.mockReturnValue(of('OK'))
|
||||
component.restore(documentsInTrash[0])
|
||||
expect(restoreSpy).toHaveBeenCalledWith([documentsInTrash[0].id])
|
||||
expect(reloadSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should support restore all documents', () => {
|
||||
it('should support restore all documents, show error if needed', () => {
|
||||
const restoreSpy = jest.spyOn(trashService, 'restoreDocuments')
|
||||
const reloadSpy = jest.spyOn(component, 'reload')
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
// fail first
|
||||
restoreSpy.mockReturnValue(throwError(() => 'Error'))
|
||||
component.restoreAll()
|
||||
expect(toastErrorSpy).toHaveBeenCalled()
|
||||
expect(reloadSpy).not.toHaveBeenCalled()
|
||||
|
||||
restoreSpy.mockReturnValue(of('OK'))
|
||||
component.restoreAll()
|
||||
expect(restoreSpy).toHaveBeenCalled()
|
||||
|
@@ -59,10 +59,16 @@ export class TrashComponent implements OnDestroy {
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
modal.componentInstance.buttonsEnabled = false
|
||||
this.trashService.emptyTrash([document.id]).subscribe(() => {
|
||||
this.toastService.showInfo($localize`Document deleted`)
|
||||
modal.close()
|
||||
this.reload()
|
||||
this.trashService.emptyTrash([document.id]).subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Document deleted`)
|
||||
modal.close()
|
||||
this.reload()
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.showError($localize`Error deleting document`, err)
|
||||
modal.close()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -83,29 +89,51 @@ export class TrashComponent implements OnDestroy {
|
||||
.subscribe(() => {
|
||||
this.trashService
|
||||
.emptyTrash(documents ? Array.from(documents) : null)
|
||||
.subscribe(() => {
|
||||
this.toastService.showInfo($localize`Document(s) deleted`)
|
||||
this.allToggled = false
|
||||
modal.close()
|
||||
this.reload()
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Document(s) deleted`)
|
||||
this.allToggled = false
|
||||
modal.close()
|
||||
this.reload()
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.showError(
|
||||
$localize`Error deleting document(s)`,
|
||||
err
|
||||
)
|
||||
modal.close()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
restore(document: Document) {
|
||||
this.trashService.restoreDocuments([document.id]).subscribe(() => {
|
||||
this.toastService.showInfo($localize`Document restored`)
|
||||
this.reload()
|
||||
this.trashService.restoreDocuments([document.id]).subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Document restored`)
|
||||
this.reload()
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.showError($localize`Error restoring document`, err)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
restoreAll(documents: Set<number> = null) {
|
||||
this.trashService
|
||||
.restoreDocuments(documents ? Array.from(documents) : null)
|
||||
.subscribe(() => {
|
||||
this.toastService.showInfo($localize`Document(s) restored`)
|
||||
this.allToggled = false
|
||||
this.reload()
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Document(s) restored`)
|
||||
this.allToggled = false
|
||||
this.reload()
|
||||
},
|
||||
error: (err) => {
|
||||
this.toastService.showError(
|
||||
$localize`Error restoring document(s)`,
|
||||
err
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
@@ -44,6 +44,7 @@ import { UsersAndGroupsComponent } from './users-groups.component'
|
||||
import { User } from 'src/app/data/user'
|
||||
import { Group } from 'src/app/data/group'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const users = [
|
||||
{ id: 1, username: 'user1', is_superuser: false },
|
||||
@@ -84,10 +85,8 @@ describe('UsersAndGroupsComponent', () => {
|
||||
PermissionsGroupComponent,
|
||||
IfOwnerDirective,
|
||||
],
|
||||
providers: [CustomDatePipe, DatePipe, PermissionsGuard],
|
||||
imports: [
|
||||
NgbModule,
|
||||
HttpClientTestingModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@@ -95,6 +94,13 @@ describe('UsersAndGroupsComponent', () => {
|
||||
NgSelectModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
PermissionsGuard,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
fixture = TestBed.createComponent(UsersAndGroupsComponent)
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
|
@@ -93,33 +93,35 @@
|
||||
</ul>
|
||||
|
||||
<div class="nav-group mt-3 mb-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
|
||||
@if (savedViewService.loading || savedViewService.sidebarViews?.length > 0) {
|
||||
@if (savedViewService.loading) {
|
||||
<h6 class="sidebar-heading px-3 text-muted">
|
||||
<span i18n>Saved views</span>
|
||||
@if (savedViewService.loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
||||
}
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
|
||||
</h6>
|
||||
} @else if (savedViewService.sidebarViews?.length > 0) {
|
||||
<h6 class="sidebar-heading px-3 text-muted">
|
||||
<span i18n>Saved views</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
||||
@for (view of savedViewService.sidebarViews; track view.id) {
|
||||
<li class="nav-item w-100 app-link" cdkDrag [cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
|
||||
cdkDragPreviewContainer="parent" cdkDragPreviewClass="navItemDrag" (cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}"
|
||||
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
|
||||
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
|
||||
popoverClass="popover-slim">
|
||||
<i-bs class="me-1" name="funnel"></i-bs><span> {{view.name}}</span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarSavedViews) {
|
||||
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
<ul class="nav flex-column mb-2" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
||||
@for (view of savedViewService.sidebarViews; track view.id) {
|
||||
<li class="nav-item w-100 app-link" cdkDrag [cdkDragDisabled]="!settingsService.organizingSidebarSavedViews"
|
||||
cdkDragPreviewContainer="parent" cdkDragPreviewClass="navItemDrag" (cdkDragStarted)="onDragStart($event)"
|
||||
(cdkDragEnded)="onDragEnd($event)">
|
||||
<a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}"
|
||||
routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name"
|
||||
[disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave"
|
||||
popoverClass="popover-slim">
|
||||
<i-bs class="me-1" name="funnel"></i-bs><span> {{view.name}}</span>
|
||||
</a>
|
||||
@if (settingsService.organizingSidebarSavedViews) {
|
||||
<div class="position-absolute end-0 top-0 px-3 py-2" [class.me-n3]="slimSidebarEnabled" cdkDragHandle>
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="nav-group mt-3 mb-1" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
@@ -214,6 +216,13 @@
|
||||
<i-bs class="me-1" name="envelope"></i-bs><span> <ng-container i18n>Mail</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
|
||||
<a class="nav-link" routerLink="trash" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Trash"
|
||||
i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body"
|
||||
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="me-1" name="trash"></i-bs><span> <ng-container i18n>Trash</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -267,13 +276,6 @@
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li class="nav-item app-link" *pngxIfPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
|
||||
<a class="nav-link" routerLink="trash" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Trash"
|
||||
i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body"
|
||||
triggers="mouseenter:mouseleave" popoverClass="popover-slim">
|
||||
<i-bs class="me-1" name="trash"></i-bs><span> <ng-container i18n>Trash</ng-container></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2" tourAnchor="tour.outro">
|
||||
<a class="px-3 py-2 text-muted small d-flex align-items-center flex-wrap text-decoration-none"
|
||||
target="_blank" rel="noopener noreferrer" href="https://docs.paperless-ngx.com" ngbPopover="Documentation"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import { AppFrameComponent } from './app-frame.component'
|
||||
import {
|
||||
@@ -37,6 +37,7 @@ import { SavedView } from 'src/app/data/saved-view'
|
||||
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { GlobalSearchComponent } from './global-search/global-search.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const saved_views = [
|
||||
{
|
||||
@@ -100,7 +101,6 @@ describe('AppFrameComponent', () => {
|
||||
GlobalSearchComponent,
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
BrowserModule,
|
||||
RouterTestingModule.withRoutes(routes),
|
||||
NgbModule,
|
||||
@@ -150,6 +150,8 @@ describe('AppFrameComponent', () => {
|
||||
},
|
||||
},
|
||||
PermissionsGuard,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -35,7 +35,6 @@ import {
|
||||
} from '@angular/cdk/drag-drop'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ProfileEditDialogComponent } from '../common/profile-edit-dialog/profile-edit-dialog.component'
|
||||
import { ObjectWithId } from 'src/app/data/object-with-id'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-app-frame',
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
|
||||
import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dialog/user-edit-dialog.component'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import {
|
||||
FILTER_FULLTEXT_QUERY,
|
||||
@@ -40,6 +40,7 @@ import { DataType } from 'src/app/data/datatype'
|
||||
import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const searchResults = {
|
||||
total: 11,
|
||||
@@ -139,13 +140,16 @@ describe('GlobalSearchComponent', () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [GlobalSearchComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbModalModule,
|
||||
NgbDropdownModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
searchService = TestBed.inject(SearchService)
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { DeletePagesConfirmDialogComponent } from './delete-pages-confirm-dialog.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { PdfViewerComponent } from 'ng2-pdf-viewer'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('DeletePagesConfirmDialogComponent', () => {
|
||||
let component: DeletePagesConfirmDialogComponent
|
||||
@@ -14,13 +15,17 @@ describe('DeletePagesConfirmDialogComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [DeletePagesConfirmDialogComponent, PdfViewerComponent],
|
||||
providers: [NgbActiveModal, SafeHtmlPipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
SafeHtmlPipe,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
fixture = TestBed.createComponent(DeletePagesConfirmDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { MergeConfirmDialogComponent } from './merge-confirm-dialog.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { of } from 'rxjs'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('MergeConfirmDialogComponent', () => {
|
||||
let component: MergeConfirmDialogComponent
|
||||
@@ -15,13 +16,16 @@ describe('MergeConfirmDialogComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MergeConfirmDialogComponent],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(MergeConfirmDialogComponent)
|
||||
|
@@ -2,8 +2,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { RotateConfirmDialogComponent } from './rotate-confirm-dialog.component'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SafeHtmlPipe } from 'src/app/pipes/safehtml.pipe'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('RotateConfirmDialogComponent', () => {
|
||||
let component: RotateConfirmDialogComponent
|
||||
@@ -12,10 +13,12 @@ describe('RotateConfirmDialogComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [RotateConfirmDialogComponent, SafeHtmlPipe],
|
||||
providers: [NgbActiveModal, SafeHtmlPipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
imports: [NgxBootstrapIconsModule.pick(allIcons)],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
SafeHtmlPipe,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="d-grid">
|
||||
<button class="btn btn-sm btn-primary" (click)="addSplit()" [disabled]="page === totalPages">
|
||||
<button class="btn btn-sm btn-primary" (click)="addSplit()" [disabled]="!canSplit">
|
||||
<i-bs name="plus-circle"></i-bs>
|
||||
<span i18n>Add Split</span>
|
||||
</button>
|
||||
|
@@ -1,13 +1,14 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
|
||||
import { SplitConfirmDialogComponent } from './split-confirm-dialog.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ReactiveFormsModule, FormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { PdfViewerModule } from 'ng2-pdf-viewer'
|
||||
import { of } from 'rxjs'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('SplitConfirmDialogComponent', () => {
|
||||
let component: SplitConfirmDialogComponent
|
||||
@@ -17,14 +18,17 @@ describe('SplitConfirmDialogComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [SplitConfirmDialogComponent],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
PdfViewerModule,
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(SplitConfirmDialogComponent)
|
||||
@@ -88,4 +92,16 @@ describe('SplitConfirmDialogComponent', () => {
|
||||
component.pdfPreviewLoaded({ numPages: 5 } as any)
|
||||
expect(component.totalPages).toEqual(5)
|
||||
})
|
||||
|
||||
it('should correctly disable split button', () => {
|
||||
component.totalPages = 5
|
||||
component.page = 1
|
||||
expect(component.canSplit).toBeTruthy()
|
||||
component.page = 5
|
||||
expect(component.canSplit).toBeFalsy()
|
||||
component.page = 4
|
||||
expect(component.canSplit).toBeTruthy()
|
||||
component['pages'] = new Set([1, 2, 3, 4])
|
||||
expect(component.canSplit).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
@@ -42,6 +42,14 @@ export class SplitConfirmDialogComponent
|
||||
public totalPages: number
|
||||
public deleteOriginal: boolean = false
|
||||
|
||||
public get canSplit(): boolean {
|
||||
return (
|
||||
this.page < this.totalPages &&
|
||||
this.pages.size < this.totalPages - 1 &&
|
||||
!this.pages.has(this.page)
|
||||
)
|
||||
}
|
||||
|
||||
public get pdfSrc(): string {
|
||||
return this.documentService.getPreviewUrl(this.documentID)
|
||||
}
|
||||
|
@@ -30,6 +30,9 @@
|
||||
<input type="checkbox" id="{{field.name}}" name="{{field.name}}" [checked]="value" value="" class="form-check-input ms-2 mt-0 pe-none">
|
||||
</div>
|
||||
}
|
||||
@case (CustomFieldDataType.Select) {
|
||||
<span [ngbTooltip]="nameTooltip">{{getSelectValue(field, value)}}</span>
|
||||
}
|
||||
@default {
|
||||
<span [ngbTooltip]="nameTooltip">{{value}}</span>
|
||||
}
|
||||
|
@@ -4,13 +4,28 @@ import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { CustomFieldDisplayComponent } from './custom-field-display.component'
|
||||
import { DisplayField, Document } from 'src/app/data/document'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const customFields: CustomField[] = [
|
||||
{ id: 1, name: 'Field 1', data_type: CustomFieldDataType.String },
|
||||
{ id: 2, name: 'Field 2', data_type: CustomFieldDataType.Monetary },
|
||||
{ id: 3, name: 'Field 3', data_type: CustomFieldDataType.DocumentLink },
|
||||
{
|
||||
id: 4,
|
||||
name: 'Field 4',
|
||||
data_type: CustomFieldDataType.Select,
|
||||
extra_data: {
|
||||
select_options: ['Option 1', 'Option 2', 'Option 3'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Field 5',
|
||||
data_type: CustomFieldDataType.Monetary,
|
||||
extra_data: { default_currency: 'JPY' },
|
||||
},
|
||||
]
|
||||
const document: Document = {
|
||||
id: 1,
|
||||
@@ -31,8 +46,12 @@ describe('CustomFieldDisplayComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CustomFieldDisplayComponent],
|
||||
providers: [DocumentService],
|
||||
imports: [HttpClientTestingModule],
|
||||
imports: [],
|
||||
providers: [
|
||||
DocumentService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
})
|
||||
|
||||
@@ -98,4 +117,20 @@ describe('CustomFieldDisplayComponent', () => {
|
||||
expect(component.currency).toEqual('EUR')
|
||||
expect(component.value).toEqual(100)
|
||||
})
|
||||
|
||||
it('should respect explicit default currency', () => {
|
||||
component['defaultCurrencyCode'] = 'EUR' // mock default locale injection
|
||||
component.fieldId = 5
|
||||
component.document = {
|
||||
id: 1,
|
||||
title: 'Doc 1',
|
||||
custom_fields: [{ field: 5, document: 1, created: null, value: '100' }],
|
||||
}
|
||||
expect(component.currency).toEqual('JPY')
|
||||
expect(component.value).toEqual(100)
|
||||
})
|
||||
|
||||
it('should show select value', () => {
|
||||
expect(component.getSelectValue(customFields[3], 2)).toEqual('Option 3')
|
||||
})
|
||||
})
|
||||
|
@@ -90,7 +90,9 @@ export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
|
||||
)?.value
|
||||
if (this.value && this.field.data_type === CustomFieldDataType.Monetary) {
|
||||
this.currency =
|
||||
this.value.match(/([A-Z]{3})/)?.[0] ?? this.defaultCurrencyCode
|
||||
this.value.match(/([A-Z]{3})/)?.[0] ??
|
||||
this.field.extra_data?.default_currency ??
|
||||
this.defaultCurrencyCode
|
||||
this.value = parseFloat(this.value.replace(this.currency, ''))
|
||||
} else if (
|
||||
this.value?.length &&
|
||||
@@ -115,6 +117,10 @@ export class CustomFieldDisplayComponent implements OnInit, OnDestroy {
|
||||
return this.docLinkDocuments?.find((d) => d.id === docId)?.title
|
||||
}
|
||||
|
||||
public getSelectValue(field: CustomField, index: number): string {
|
||||
return field.extra_data.select_options[index]
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.unsubscribeNotifier.next(true)
|
||||
this.unsubscribeNotifier.complete()
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { of } from 'rxjs'
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { CustomFieldEditDialogComponent } from '../edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const fields: CustomField[] = [
|
||||
{
|
||||
@@ -47,7 +48,6 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [CustomFieldsDropdownComponent, SelectComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@@ -55,6 +55,10 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
NgbDropdownModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
})
|
||||
customFieldService = TestBed.inject(CustomFieldsService)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
DateSelection,
|
||||
RelativeDate,
|
||||
} from './dates-dropdown.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ClearableBadgeComponent } from '../clearable-badge/clearable-badge.component'
|
||||
@@ -18,6 +18,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('DatesDropdownComponent', () => {
|
||||
let component: DatesDropdownComponent
|
||||
@@ -31,14 +32,19 @@ describe('DatesDropdownComponent', () => {
|
||||
ClearableBadgeComponent,
|
||||
CustomDatePipe,
|
||||
],
|
||||
providers: [SettingsService, CustomDatePipe, DatePipe],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgbModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
SettingsService,
|
||||
CustomDatePipe,
|
||||
DatePipe,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
settingsService = TestBed.inject(SettingsService)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -11,6 +11,7 @@ import { SelectComponent } from '../../input/select/select.component'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('CorrespondentEditDialogComponent', () => {
|
||||
let component: CorrespondentEditDialogComponent
|
||||
@@ -27,13 +28,11 @@ describe('CorrespondentEditDialogComponent', () => {
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -13,6 +13,28 @@
|
||||
@if (typeFieldDisabled) {
|
||||
<small class="d-block mt-n2" i18n>Data type cannot be changed after a field is created</small>
|
||||
}
|
||||
<div [formGroup]="objectForm.controls.extra_data">
|
||||
@switch (objectForm.get('data_type').value) {
|
||||
@case (CustomFieldDataType.Select) {
|
||||
<button type="button" class="btn btn-sm btn-primary my-2" (click)="addSelectOption()">
|
||||
<span i18n>Add option</span> <i-bs name="plus-circle"></i-bs>
|
||||
</button>
|
||||
<div formArrayName="select_options">
|
||||
@for (option of objectForm.controls.extra_data.controls.select_options.controls; track option; let i = $index) {
|
||||
<div class="input-group input-group-sm my-2">
|
||||
<input #selectOption type="text" class="form-control" [formControl]="option" autocomplete="off">
|
||||
<button type="button" class="btn btn-outline-danger" (click)="removeSelectOption(i)" i18n>Delete</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@case (CustomFieldDataType.Monetary) {
|
||||
<div class="my-3">
|
||||
<pngx-input-text i18n-title title="Default Currency" hint="3-character currency code" i18n-hint formControlName="default_currency" placeholder="Use locale" i18n-placeholder autocomplete="off"></pngx-input-text>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="cancel()" i18n [disabled]="networkActive">Cancel</button>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
|
||||
import { CustomFieldEditDialogComponent } from './custom-field-edit-dialog.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
@@ -12,6 +12,10 @@ import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { SelectComponent } from '../../input/select/select.component'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { CustomFieldDataType } from 'src/app/data/custom-field'
|
||||
import { ElementRef, QueryList } from '@angular/core'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
|
||||
describe('CustomFieldEditDialogComponent', () => {
|
||||
let component: CustomFieldEditDialogComponent
|
||||
@@ -28,13 +32,17 @@ describe('CustomFieldEditDialogComponent', () => {
|
||||
TextComponent,
|
||||
SafeHtmlPipe,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
@@ -64,4 +72,55 @@ describe('CustomFieldEditDialogComponent', () => {
|
||||
component.ngOnInit()
|
||||
expect(component.objectForm.get('data_type').disabled).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should initialize select options on edit', () => {
|
||||
component.dialogMode = EditDialogMode.EDIT
|
||||
component.object = {
|
||||
id: 1,
|
||||
name: 'Field 1',
|
||||
data_type: CustomFieldDataType.Select,
|
||||
extra_data: {
|
||||
select_options: ['Option 1', 'Option 2', 'Option 3'],
|
||||
},
|
||||
}
|
||||
fixture.detectChanges()
|
||||
component.ngOnInit()
|
||||
expect(
|
||||
component.objectForm.get('extra_data').get('select_options').value.length
|
||||
).toBe(3)
|
||||
})
|
||||
|
||||
it('should support add / remove select options', () => {
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
fixture.detectChanges()
|
||||
component.ngOnInit()
|
||||
expect(
|
||||
component.objectForm.get('extra_data').get('select_options').value.length
|
||||
).toBe(1)
|
||||
component.addSelectOption()
|
||||
expect(
|
||||
component.objectForm.get('extra_data').get('select_options').value.length
|
||||
).toBe(2)
|
||||
component.addSelectOption()
|
||||
expect(
|
||||
component.objectForm.get('extra_data').get('select_options').value.length
|
||||
).toBe(3)
|
||||
component.removeSelectOption(0)
|
||||
expect(
|
||||
component.objectForm.get('extra_data').get('select_options').value.length
|
||||
).toBe(2)
|
||||
})
|
||||
|
||||
it('should focus on last select option input', () => {
|
||||
const selectOptionInputs = component[
|
||||
'selectOptionInputs'
|
||||
] as QueryList<ElementRef>
|
||||
component.dialogMode = EditDialogMode.CREATE
|
||||
component.objectForm.get('data_type').setValue(CustomFieldDataType.Select)
|
||||
component.ngOnInit()
|
||||
component.ngAfterViewInit()
|
||||
component.addSelectOption()
|
||||
fixture.detectChanges()
|
||||
expect(document.activeElement).toBe(selectOptionInputs.last.nativeElement)
|
||||
})
|
||||
})
|
||||
|
@@ -1,11 +1,24 @@
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { FormGroup, FormControl } from '@angular/forms'
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
QueryList,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { FormGroup, FormControl, FormArray } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { DATA_TYPE_LABELS, CustomField } from 'src/app/data/custom-field'
|
||||
import {
|
||||
DATA_TYPE_LABELS,
|
||||
CustomField,
|
||||
CustomFieldDataType,
|
||||
} from 'src/app/data/custom-field'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-custom-field-edit-dialog',
|
||||
@@ -14,8 +27,20 @@ import { EditDialogComponent, EditDialogMode } from '../edit-dialog.component'
|
||||
})
|
||||
export class CustomFieldEditDialogComponent
|
||||
extends EditDialogComponent<CustomField>
|
||||
implements OnInit
|
||||
implements OnInit, AfterViewInit, OnDestroy
|
||||
{
|
||||
CustomFieldDataType = CustomFieldDataType
|
||||
|
||||
@ViewChildren('selectOption')
|
||||
private selectOptionInputs: QueryList<ElementRef>
|
||||
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
private get selectOptions(): FormArray {
|
||||
return (this.objectForm.controls.extra_data as FormGroup).controls
|
||||
.select_options as FormArray
|
||||
}
|
||||
|
||||
constructor(
|
||||
service: CustomFieldsService,
|
||||
activeModal: NgbActiveModal,
|
||||
@@ -30,6 +55,25 @@ export class CustomFieldEditDialogComponent
|
||||
if (this.typeFieldDisabled) {
|
||||
this.objectForm.get('data_type').disable()
|
||||
}
|
||||
if (this.object?.data_type === CustomFieldDataType.Select) {
|
||||
this.selectOptions.clear()
|
||||
this.object.extra_data.select_options.forEach((option) =>
|
||||
this.selectOptions.push(new FormControl(option))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.selectOptionInputs.changes
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
this.selectOptionInputs.last.nativeElement.focus()
|
||||
})
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.unsubscribeNotifier.next(true)
|
||||
this.unsubscribeNotifier.complete()
|
||||
}
|
||||
|
||||
getCreateTitle() {
|
||||
@@ -44,6 +88,10 @@ export class CustomFieldEditDialogComponent
|
||||
return new FormGroup({
|
||||
name: new FormControl(null),
|
||||
data_type: new FormControl(null),
|
||||
extra_data: new FormGroup({
|
||||
select_options: new FormArray([new FormControl(null)]),
|
||||
default_currency: new FormControl(null),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,4 +102,12 @@ export class CustomFieldEditDialogComponent
|
||||
get typeFieldDisabled(): boolean {
|
||||
return this.dialogMode === EditDialogMode.EDIT
|
||||
}
|
||||
|
||||
public addSelectOption() {
|
||||
this.selectOptions.push(new FormControl(''))
|
||||
}
|
||||
|
||||
public removeSelectOption(index: number) {
|
||||
this.selectOptions.removeAt(index)
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="col">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Matching algorithm" [items]="getMatchingAlgorithms()" formControlName="matching_algorithm"></pngx-input-select>
|
||||
@if (patternRequired) {
|
||||
<pngx-input-text i18n-title title="Matching pattern" formControlName="match" [error]="error?.match"></pngx-input-text>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -11,6 +11,7 @@ import { SelectComponent } from '../../input/select/select.component'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('DocumentTypeEditDialogComponent', () => {
|
||||
let component: DocumentTypeEditDialogComponent
|
||||
@@ -27,13 +28,11 @@ describe('DocumentTypeEditDialogComponent', () => {
|
||||
TextComponent,
|
||||
PermissionsFormComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
HttpTestingController,
|
||||
HttpClientTestingModule,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import { Component } from '@angular/core'
|
||||
import {
|
||||
@@ -30,6 +30,7 @@ import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { EditDialogComponent, EditDialogMode } from './edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -96,6 +97,7 @@ describe('EditDialogComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TestComponent],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
@@ -114,8 +116,9 @@ describe('EditDialogComponent', () => {
|
||||
},
|
||||
SettingsService,
|
||||
TagService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
imports: [HttpClientTestingModule, FormsModule, ReactiveFormsModule],
|
||||
}).compileComponents()
|
||||
|
||||
tagService = TestBed.inject(TagService)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -12,6 +12,8 @@ import { TextComponent } from '../../input/text/text.component'
|
||||
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { GroupEditDialogComponent } from './group-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
|
||||
describe('GroupEditDialogComponent', () => {
|
||||
let component: GroupEditDialogComponent
|
||||
@@ -29,13 +31,17 @@ describe('GroupEditDialogComponent', () => {
|
||||
PermissionsFormComponent,
|
||||
PermissionsSelectComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
HttpTestingController,
|
||||
HttpClientTestingModule,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
@@ -23,6 +23,7 @@ import { SelectComponent } from '../../input/select/select.component'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { MailAccountEditDialogComponent } from './mail-account-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('MailAccountEditDialogComponent', () => {
|
||||
let component: MailAccountEditDialogComponent
|
||||
@@ -42,13 +43,11 @@ describe('MailAccountEditDialogComponent', () => {
|
||||
PermissionsFormComponent,
|
||||
PasswordComponent,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -10,36 +10,57 @@
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<pngx-input-text i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||
<pngx-input-select i18n-title title="Account" [items]="accounts" formControlName="account"></pngx-input-select>
|
||||
<pngx-input-text i18n-title title="Folder" formControlName="folder" i18n-hint hint="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server." [error]="error?.folder"></pngx-input-text>
|
||||
<pngx-input-number i18n-title title="Maximum age (days)" formControlName="maximum_age" [showAdd]="false" [error]="error?.maximum_age"></pngx-input-number>
|
||||
<pngx-input-select i18n-title title="Attachment type" [items]="attachmentTypeOptions" formControlName="attachment_type"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Consumption scope" [items]="consumptionScopeOptions" formControlName="consumption_scope" i18n-hint hint="See docs for .eml processing requirements"></pngx-input-select>
|
||||
<pngx-input-number i18n-title title="Rule order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Name" formControlName="name" [error]="error?.name" autocomplete="off"></pngx-input-text>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<p class="small" i18n>Paperless will only process mails that match <em>all</em> of the filters specified below.</p>
|
||||
<pngx-input-text i18n-title title="Filter from" formControlName="filter_from" [error]="error?.filter_from"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter to" formControlName="filter_to" [error]="error?.filter_to"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter subject" formControlName="filter_subject" [error]="error?.filter_subject"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter body" formControlName="filter_body" [error]="error?.filter_body"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter attachment filename includes" formControlName="filter_attachment_filename_include" i18n-hint hint="Only consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_attachment_filename_include"></pngx-input-text>
|
||||
<pngx-input-text i18n-title title="Filter attachment filename excluding" formControlName="filter_attachment_filename_exclude" i18n-hint hint="Do not consume documents which entirely match this filename if specified. Wildcards such as *.pdf or *invoice* are allowed. Case insensitive." [error]="error?.filter_attachment_filename_exclude"></pngx-input-text>
|
||||
<pngx-input-number [horizontal]="true" i18n-title title="Rule order" formControlName="order" [showAdd]="false" [error]="error?.order"></pngx-input-number>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<pngx-input-select i18n-title title="Action" [items]="actionOptions" formControlName="action" i18n-hint hint="Action is only performed when documents are consumed from the mail. Mails without attachments remain entirely untouched."></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Account" [items]="accounts" formControlName="account"></pngx-input-select>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mt-0"/>
|
||||
<div class="row">
|
||||
<p class="small" i18n>Paperless will only process mails that match <em>all</em> of the criteria specified below.</p>
|
||||
<div class="col-md-6">
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Folder" formControlName="folder" i18n-hint hint="Subfolders must be separated by a delimiter, often a dot ('.') or slash ('/'), but it varies by mail server." [error]="error?.folder"></pngx-input-text>
|
||||
<pngx-input-number [horizontal]="true" i18n-title title="Maximum age (days)" formControlName="maximum_age" [showAdd]="false" [error]="error?.maximum_age"></pngx-input-number>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Filter from" formControlName="filter_from" [error]="error?.filter_from"></pngx-input-text>
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Filter to" formControlName="filter_to" [error]="error?.filter_to"></pngx-input-text>
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Filter subject" formControlName="filter_subject" [error]="error?.filter_subject"></pngx-input-text>
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Filter body" formControlName="filter_body" [error]="error?.filter_body"></pngx-input-text>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mt-0"/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Consumption scope" [items]="consumptionScopeOptions" formControlName="consumption_scope" i18n-hint hint="See docs for .eml processing requirements"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Attachment type" [items]="attachmentTypeOptions" formControlName="attachment_type"></pngx-input-select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Include only files matching" formControlName="filter_attachment_filename_include" i18n-hint hint="Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive." [error]="error?.filter_attachment_filename_include"></pngx-input-text>
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Exclude files matching" formControlName="filter_attachment_filename_exclude" i18n-hint hint="Optional. Wildcards e.g. *.pdf or *invoice* allowed. Can be comma-separated list. Case insensitive." [error]="error?.filter_attachment_filename_exclude"></pngx-input-text>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mt-0"/>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Action" [items]="actionOptions" formControlName="action" i18n-hint hint="Only performed if the mail is processed."></pngx-input-select>
|
||||
@if (showActionParamField) {
|
||||
<pngx-input-text i18n-title title="Action parameter" formControlName="action_parameter" [error]="error?.action_parameter"></pngx-input-text>
|
||||
<pngx-input-text [horizontal]="true" i18n-title title="Action parameter" formControlName="action_parameter" [error]="error?.action_parameter"></pngx-input-text>
|
||||
}
|
||||
<pngx-input-select i18n-title title="Assign title from" [items]="metadataTitleOptions" formControlName="assign_title_from"></pngx-input-select>
|
||||
<pngx-input-tags [allowCreate]="false" formControlName="assign_tags"></pngx-input-tags>
|
||||
<pngx-input-select i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Assign correspondent from" [items]="metadataCorrespondentOptions" formControlName="assign_correspondent_from"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign title from" [items]="metadataTitleOptions" formControlName="assign_title_from"></pngx-input-select>
|
||||
<pngx-input-check [horizontal]="true" i18n-title title="Assign owner from rule" formControlName="assign_owner_from_rule"></pngx-input-check>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<pngx-input-tags [horizontal]="true" [allowCreate]="false" formControlName="assign_tags"></pngx-input-tags>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign document type" [items]="documentTypes" [allowNull]="true" formControlName="assign_document_type"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign correspondent from" [items]="metadataCorrespondentOptions" formControlName="assign_correspondent_from"></pngx-input-select>
|
||||
@if (showCorrespondentField) {
|
||||
<pngx-input-select i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
<pngx-input-select [horizontal]="true" i18n-title title="Assign correspondent" [items]="correspondents" [allowNull]="true" formControlName="assign_correspondent"></pngx-input-select>
|
||||
}
|
||||
<pngx-input-check i18n-title title="Assign owner from rule" formControlName="assign_owner_from_rule"></pngx-input-check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -23,6 +23,7 @@ import { TagsComponent } from '../../input/tags/tags.component'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { MailRuleEditDialogComponent } from './mail-rule-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('MailRuleEditDialogComponent', () => {
|
||||
let component: MailRuleEditDialogComponent
|
||||
@@ -43,6 +44,7 @@ describe('MailRuleEditDialogComponent', () => {
|
||||
SafeHtmlPipe,
|
||||
CheckComponent,
|
||||
],
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
@@ -63,13 +65,8 @@ describe('MailRuleEditDialogComponent', () => {
|
||||
listAll: () => of([]),
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -12,6 +12,7 @@ import { SelectComponent } from '../../input/select/select.component'
|
||||
import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { StoragePathEditDialogComponent } from './storage-path-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('StoragePathEditDialogComponent', () => {
|
||||
let component: StoragePathEditDialogComponent
|
||||
@@ -29,13 +30,11 @@ describe('StoragePathEditDialogComponent', () => {
|
||||
PermissionsFormComponent,
|
||||
SafeHtmlPipe,
|
||||
],
|
||||
providers: [NgbActiveModal],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -14,6 +14,7 @@ import { TextComponent } from '../../input/text/text.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { TagEditDialogComponent } from './tag-edit-dialog.component'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('TagEditDialogComponent', () => {
|
||||
let component: TagEditDialogComponent
|
||||
@@ -32,15 +33,19 @@ describe('TagEditDialogComponent', () => {
|
||||
ColorComponent,
|
||||
CheckComponent,
|
||||
],
|
||||
providers: [NgbActiveModal, SettingsService],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
SettingsService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(TagEditDialogComponent)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
@@ -19,6 +19,8 @@ import { TextComponent } from '../../input/text/text.component'
|
||||
import { PermissionsSelectComponent } from '../../permissions-select/permissions-select.component'
|
||||
import { EditDialogMode } from '../edit-dialog.component'
|
||||
import { UserEditDialogComponent } from './user-edit-dialog.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
|
||||
describe('UserEditDialogComponent', () => {
|
||||
let component: UserEditDialogComponent
|
||||
@@ -37,6 +39,13 @@ describe('UserEditDialogComponent', () => {
|
||||
PermissionsFormComponent,
|
||||
PermissionsSelectComponent,
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
@@ -54,13 +63,8 @@ describe('UserEditDialogComponent', () => {
|
||||
},
|
||||
},
|
||||
SettingsService,
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -140,7 +140,7 @@
|
||||
</div>
|
||||
@if (formGroup.get('type').value === WorkflowTriggerType.DocumentAdded || formGroup.get('type').value === WorkflowTriggerType.DocumentUpdated) {
|
||||
<div class="col-md-6">
|
||||
<pngx-input-tags [allowCreate]="false" i18n-title title="Has tags" formControlName="filter_has_tags"></pngx-input-tags>
|
||||
<pngx-input-tags [allowCreate]="false" i18n-title title="Has any of tags" formControlName="filter_has_tags"></pngx-input-tags>
|
||||
<pngx-input-select i18n-title title="Has correspondent" [items]="correspondents" [allowNull]="true" formControlName="filter_has_correspondent"></pngx-input-select>
|
||||
<pngx-input-select i18n-title title="Has document type" [items]="documentTypes" [allowNull]="true" formControlName="filter_has_document_type"></pngx-input-select>
|
||||
</div>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
} from 'src/app/data/workflow-action'
|
||||
import { MATCHING_ALGORITHMS, MATCH_AUTO } from 'src/app/data/matching-model'
|
||||
import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const workflow: Workflow = {
|
||||
name: 'Workflow 1',
|
||||
@@ -88,6 +89,7 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
SafeHtmlPipe,
|
||||
ConfirmButtonComponent,
|
||||
],
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule, NgbModule],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{
|
||||
@@ -150,13 +152,8 @@ describe('WorkflowEditDialogComponent', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgSelectModule,
|
||||
NgbModule,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import {
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { DateComponent } from './date.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import {
|
||||
NgbDateParserFormatter,
|
||||
NgbDatepickerModule,
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { LocalizedDateParserFormatter } from 'src/app/utils/ngb-date-parser-formatter'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('DateComponent', () => {
|
||||
let component: DateComponent
|
||||
@@ -22,19 +23,20 @@ describe('DateComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DateComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbDatepickerModule,
|
||||
RouterTestingModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: NgbDateParserFormatter,
|
||||
useClass: LocalizedDateParserFormatter,
|
||||
},
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule,
|
||||
NgbDatepickerModule,
|
||||
RouterTestingModule,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
FormsModule,
|
||||
@@ -10,6 +10,7 @@ import { of, throwError } from 'rxjs'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { DocumentLinkComponent } from './document-link.component'
|
||||
import { FILTER_TITLE } from 'src/app/data/filter-rule-type'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
const documents = [
|
||||
{
|
||||
@@ -38,11 +39,10 @@ describe('DocumentLinkComponent', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DocumentLinkComponent],
|
||||
imports: [
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
imports: [NgSelectModule, FormsModule, ReactiveFormsModule],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
})
|
||||
documentService = TestBed.inject(DocumentService)
|
||||
|
@@ -13,10 +13,10 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row mt-2 align-items-center bg-light p-2">
|
||||
<div class="d-flex flex-row gap-2 w-100 mh-1" style="min-height: 1em;"
|
||||
<div class="mt-2 align-items-center bg-light p-2">
|
||||
<div class="d-flex flex-wrap flex-row gap-2 w-100"
|
||||
cdkDropList #unselectedList="cdkDropList"
|
||||
cdkDropListOrientation="horizontal"
|
||||
cdkDropListOrientation="mixed"
|
||||
(cdkDropListDropped)="drop($event)"
|
||||
[cdkDropListConnectedTo]="[selectedList]">
|
||||
@for (item of unselectedItems; track item.id) {
|
||||
|
@@ -1,7 +1,3 @@
|
||||
.badge {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
|
||||
import { FileComponent } from './file.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('FileComponent', () => {
|
||||
let component: FileComponent
|
||||
@@ -11,7 +12,11 @@ describe('FileComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [FileComponent],
|
||||
imports: [FormsModule, ReactiveFormsModule, HttpClientTestingModule],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(FileComponent)
|
||||
|
@@ -4,9 +4,10 @@ import {
|
||||
NG_VALUE_ACCESSOR,
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { CurrencyPipe } from '@angular/common'
|
||||
import { MonetaryComponent } from './monetary.component'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('MonetaryComponent', () => {
|
||||
let component: MonetaryComponent
|
||||
@@ -15,8 +16,12 @@ describe('MonetaryComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [MonetaryComponent],
|
||||
providers: [CurrencyPipe],
|
||||
imports: [FormsModule, ReactiveFormsModule, HttpClientTestingModule],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
providers: [
|
||||
CurrencyPipe,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(MonetaryComponent)
|
||||
@@ -47,6 +52,11 @@ describe('MonetaryComponent', () => {
|
||||
expect(component.defaultCurrencyCode).toEqual('BRL')
|
||||
})
|
||||
|
||||
it('should support setting a default currency code', () => {
|
||||
component.defaultCurrency = 'EUR'
|
||||
expect(component.defaultCurrencyCode).toEqual('EUR')
|
||||
})
|
||||
|
||||
it('should parse monetary value correctly', () => {
|
||||
expect(component['parseMonetaryValue']('123.4')).toEqual('123.4')
|
||||
expect(component['parseMonetaryValue']('123.4', true)).toEqual('123.40')
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, forwardRef, Inject, LOCALE_ID } from '@angular/core'
|
||||
import { Component, forwardRef, Inject, Input, LOCALE_ID } from '@angular/core'
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { AbstractInputComponent } from '../abstract-input'
|
||||
import { getLocaleCurrencyCode } from '@angular/common'
|
||||
@@ -29,11 +29,16 @@ export class MonetaryComponent extends AbstractInputComponent<string> {
|
||||
|
||||
defaultCurrencyCode: string
|
||||
|
||||
@Input()
|
||||
set defaultCurrency(currency: string) {
|
||||
if (currency) this.defaultCurrencyCode = currency
|
||||
}
|
||||
|
||||
constructor(@Inject(LOCALE_ID) currentLocale: string) {
|
||||
super()
|
||||
|
||||
this.currency = this.defaultCurrencyCode =
|
||||
getLocaleCurrencyCode(currentLocale)
|
||||
this.defaultCurrency ?? getLocaleCurrencyCode(currentLocale)
|
||||
}
|
||||
|
||||
writeValue(newValue: any): void {
|
||||
|
@@ -6,8 +6,9 @@ import {
|
||||
} from '@angular/forms'
|
||||
import { NumberComponent } from './number.component'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { of } from 'rxjs'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('NumberComponent', () => {
|
||||
let component: NumberComponent
|
||||
@@ -18,8 +19,12 @@ describe('NumberComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [NumberComponent],
|
||||
providers: [DocumentService],
|
||||
imports: [FormsModule, ReactiveFormsModule, HttpClientTestingModule],
|
||||
imports: [FormsModule, ReactiveFormsModule],
|
||||
providers: [
|
||||
DocumentService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(NumberComponent)
|
||||
|
@@ -9,8 +9,9 @@ import { SelectComponent } from '../../select/select.component'
|
||||
import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { PermissionsGroupComponent } from '../permissions-group/permissions-group.component'
|
||||
import { PermissionsUserComponent } from '../permissions-user/permissions-user.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('PermissionsFormComponent', () => {
|
||||
let component: PermissionsFormComponent
|
||||
@@ -24,14 +25,16 @@ describe('PermissionsFormComponent', () => {
|
||||
PermissionsGroupComponent,
|
||||
PermissionsUserComponent,
|
||||
],
|
||||
providers: [],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
NgbAccordionModule,
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
],
|
||||
providers: [
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(PermissionsFormComponent)
|
||||
|
@@ -5,10 +5,11 @@ import {
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { PermissionsGroupComponent } from './permissions-group.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { of } from 'rxjs'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('PermissionsGroupComponent', () => {
|
||||
let component: PermissionsGroupComponent
|
||||
@@ -19,12 +20,11 @@ describe('PermissionsGroupComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PermissionsGroupComponent],
|
||||
providers: [GroupService],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule],
|
||||
providers: [
|
||||
GroupService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -5,11 +5,12 @@ import {
|
||||
ReactiveFormsModule,
|
||||
} from '@angular/forms'
|
||||
import { PermissionsUserComponent } from './permissions-user.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { provideHttpClientTesting } from '@angular/common/http/testing'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { of } from 'rxjs'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
|
||||
|
||||
describe('PermissionsUserComponent', () => {
|
||||
let component: PermissionsUserComponent
|
||||
@@ -20,12 +21,11 @@ describe('PermissionsUserComponent', () => {
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PermissionsUserComponent],
|
||||
providers: [UserService],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientTestingModule,
|
||||
NgSelectModule,
|
||||
imports: [FormsModule, ReactiveFormsModule, NgSelectModule],
|
||||
providers: [
|
||||
UserService,
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
|
@@ -27,3 +27,11 @@
|
||||
background-position: right calc(0.375em + 0.1875rem) center !important;
|
||||
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) !important;
|
||||
}
|
||||
|
||||
.input-group .ng-select-taggable:first-child:nth-last-child(2) {
|
||||
max-width: calc(100% - 45px); // fudge factor for (1x) ng-select button width
|
||||
}
|
||||
|
||||
.input-group .ng-select-taggable:first-child:nth-last-child(3) {
|
||||
max-width: calc(100% - 90px); // fudge factor for (2x) ng-select button width
|
||||
}
|
||||
|
@@ -132,4 +132,12 @@ describe('SelectComponent', () => {
|
||||
const expectedTitle = `Filter documents with this ${component.title}`
|
||||
expect(component.filterButtonTitle).toEqual(expectedTitle)
|
||||
})
|
||||
|
||||
it('should support setting items as a plain array', () => {
|
||||
component.itemsArray = ['foo', 'bar']
|
||||
expect(component.items).toEqual([
|
||||
{ id: 0, name: 'foo' },
|
||||
{ id: 1, name: 'bar' },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
@@ -34,6 +34,11 @@ export class SelectComponent extends AbstractInputComponent<number> {
|
||||
if (items && this.value) this.checkForPrivateItems(this.value)
|
||||
}
|
||||
|
||||
@Input()
|
||||
set itemsArray(items: any[]) {
|
||||
this._items = items.map((item, index) => ({ id: index, name: item }))
|
||||
}
|
||||
|
||||
writeValue(newValue: any): void {
|
||||
if (newValue && this._items) {
|
||||
this.checkForPrivateItems(newValue)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user