Compare commits

..
209 changed files with 9994 additions and 37679 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
COMPOSE_PROJECT_NAME=paperless
COMPOSE_PROJECT_NAME=paperless
@@ -1,37 +0,0 @@
#!/bin/bash
# Verify that all text files end in a trailing newline.
# Exit on first failing command.
set -e
# Exit on unset variable.
set -u
success=0
function is_plaintext_file() {
local file="$1"
if [[ $file == *.svg ]]; then
echo ""
return
fi
file --brief "${file}" | grep text
}
# Split strings on newlines.
IFS='
'
for file in $(git ls-files)
do
if [[ -z $(is_plaintext_file "${file}") ]]; then
continue
fi
if ! [[ -z "$(tail -c 1 "${file}")" ]]; then
printf "File must end in a trailing newline: %s\n" "${file}" >&2
success=255
fi
done
exit "${success}"
@@ -1,26 +0,0 @@
#!/bin/bash
# Check for trailing whitespace at end of lines.
# Exit on first failing command.
set -e
# Exit on unset variable.
set -u
FOUND_TRAILING_WHITESPACE=0
while read -r line; do
if grep \
"\s$" \
--line-number \
--with-filename \
--binary-files=without-match \
--exclude="*.svg" \
--exclude="*.eps" \
"${line}"; then
echo "ERROR: Found trailing whitespace" >&2;
FOUND_TRAILING_WHITESPACE=1
fi
done < <(git ls-files)
exit "${FOUND_TRAILING_WHITESPACE}"
+7 -21
View File
@@ -20,7 +20,7 @@ jobs:
name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: 3.7
-
name: Get pip cache dir
id: pip-cache
@@ -59,7 +59,7 @@ jobs:
name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: 3.7
-
name: Get pip cache dir
id: pip-cache
@@ -81,26 +81,12 @@ jobs:
run: |
cd src/
pycodestyle
whitespace:
runs-on: ubuntu-20.04
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Ensure there are no trailing spaces
run: |
.github/workflow-scripts/check-trailing-whitespace
-
name: Ensure all text files end with a trailing newline
run: |
.github/workflow-scripts/check-trailing-whitespace
tests:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ['3.7', '3.8', '3.9']
python-version: ['3.6', '3.7', '3.8', '3.9']
fail-fast: false
steps:
-
@@ -136,7 +122,7 @@ jobs:
pytest
-
name: Publish coverage results
if: matrix.python-version == '3.9'
if: matrix.python-version == '3.8'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# https://github.com/coveralls-clients/coveralls-python/issues/251
@@ -172,7 +158,7 @@ jobs:
path: src/documents/static/frontend/
build-release:
needs: [frontend, documentation, tests, whitespace, codestyle]
needs: [frontend, documentation, tests, codestyle]
runs-on: ubuntu-20.04
steps:
-
@@ -182,7 +168,7 @@ jobs:
name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: 3.7
-
name: Install dependencies
run: |
@@ -282,7 +268,7 @@ jobs:
build-docker-image:
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/heads/feature-') || github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/ng-'))
runs-on: ubuntu-latest
needs: [frontend, tests, whitespace, codestyle]
needs: [frontend, tests, codestyle]
steps:
-
name: Prepare
+15 -15
View File
@@ -8,18 +8,19 @@ RUN git clone https://github.com/agl/jbig2enc .
RUN ./autogen.sh
RUN ./configure && make
FROM python:3.9-slim-bullseye
FROM python:3.7-slim
# Binary dependencies
RUN apt-get update \
&& apt-get -y --no-install-recommends install \
RUN echo "deb http://deb.debian.org/debian bullseye main" > /etc/apt/sources.list.d/bullseye.list \
&& apt-get update \
&& apt-get -y --no-install-recommends install \
# Basic dependencies
curl \
gnupg \
imagemagick \
gettext \
sudo \
tzdata \
gosu \
# fonts for text file thumbnail generation
fonts-liberation \
# for Numpy
@@ -27,18 +28,16 @@ RUN apt-get update \
libxslt1-dev \
# thumbnail size reduction
optipng \
libxml2 \
pngquant \
unpaper \
zlib1g \
ghostscript \
icc-profiles-free \
# Mime type detection
# Mime type detection
file \
libmagic-dev \
media-types \
# OCRmyPDF dependencies
# OCRmyPDF dependencies
ghostscript \
icc-profiles-free \
liblept5 \
libxml2 \
pngquant \
qpdf \
tesseract-ocr \
tesseract-ocr-eng \
@@ -46,6 +45,8 @@ RUN apt-get update \
tesseract-ocr-fra \
tesseract-ocr-ita \
tesseract-ocr-spa \
unpaper \
zlib1g \
&& rm -rf /var/lib/apt/lists/*
# copy jbig2enc
@@ -77,7 +78,6 @@ RUN cd docker \
&& mkdir /var/log/supervisord /var/run/supervisord \
&& cp supervisord.conf /etc/supervisord.conf \
&& cp docker-entrypoint.sh /sbin/docker-entrypoint.sh \
&& cp docker-prepare.sh /sbin/docker-prepare.sh \
&& chmod 755 /sbin/docker-entrypoint.sh \
&& chmod +x install_management_commands.sh \
&& ./install_management_commands.sh \
@@ -93,8 +93,8 @@ COPY src/ ./
RUN addgroup --gid 1000 paperless \
&& useradd --uid 1000 --gid paperless --home-dir /usr/src/paperless paperless \
&& chown -R paperless:paperless ../ \
&& gosu paperless python3 manage.py collectstatic --clear --no-input \
&& gosu paperless python3 manage.py compilemessages
&& sudo -HEu paperless python3 manage.py collectstatic --clear --no-input \
&& sudo -HEu paperless python3 manage.py compilemessages
VOLUME ["/usr/src/paperless/data", "/usr/src/paperless/media", "/usr/src/paperless/consume", "/usr/src/paperless/export"]
ENTRYPOINT ["/sbin/docker-entrypoint.sh"]
+10 -8
View File
@@ -10,18 +10,19 @@ name = "piwheels"
[packages]
dateparser = "~=1.0.0"
django = "~=3.2"
django = "~=3.1.3"
django-cors-headers = "*"
django-extensions = "*"
django-filter = "~=2.4.0"
django-q = "~=1.3.4"
django-q = "==1.3.4"
djangorestframework = "~=3.12.2"
filelock = "*"
fuzzywuzzy = {extras = ["speedup"], version = "*"}
gunicorn = "*"
imap-tools = "*"
langdetect = "*"
numpy = "~=1.20.0"
# numpy 1.20.0 drops python 3.6 support
numpy = "~=1.19.5"
pathvalidate = "*"
pillow = "~=8.1"
pikepdf = "~=2.5"
@@ -33,11 +34,13 @@ psycopg2-binary = "*"
redis = "*"
# Pinned because aarch64 wheels and updates cause warnings when loading the classifier model.
scikit-learn="==0.24.0"
whitenoise = "~=5.3.0"
watchdog = "~=2.1.0"
# Prevent scipy updates because 1.6 is incompatible with python 3.6
scipy="~=1.5.4"
whitenoise = "~=5.2.0"
watchdog = "~=1.0.0"
whoosh="~=2.7.4"
inotifyrecursive = "~=0.3.4"
ocrmypdf = "~=12.3"
ocrmypdf = "~=11.6"
tqdm = "*"
tika = "*"
# TODO: This will sadly also install daphne+dependencies,
@@ -47,10 +50,9 @@ channels-redis = "*"
uvicorn = {extras = ["standard"], version = "*"}
concurrent-log-handler = "*"
# uvloop 0.15+ incompatible with python 3.6
uvloop = "~=0.15"
uvloop = "~=0.14.0"
cryptography = "~=3.4"
"pdfminer.six" = "*"
"backports.zoneinfo" = "*"
[dev-packages]
coveralls = "*"
Generated
+781 -872
View File
File diff suppressed because it is too large. Load diff
+14 -17
View File
@@ -6,22 +6,18 @@
[![Docker Hub Pulls](https://img.shields.io/docker/pulls/jonaswinkler/paperless-ng.svg)](https://hub.docker.com/r/jonaswinkler/paperless-ng)
[![Coverage Status](https://coveralls.io/repos/github/jonaswinkler/paperless-ng/badge.svg?branch=master)](https://coveralls.io/github/jonaswinkler/paperless-ng?branch=master)
# Archive notice
This project is not dead! Rather, this project is now maintained as a community effort at https://github.com/paperless-ngx/paperless-ngx, a direct continuation of this project and drop-in replacement. Please see their project page and the documentation at https://docs.paperless-ngx.com/ for details.
# Paperless-ng
[Paperless (click me)](https://github.com/the-paperless-project/paperless) is an application by Daniel Quinn and contributors that indexes your scanned documents and allows you to easily search for documents and store metadata alongside your documents.
Paperless-ng is a fork of the original project, adding a new interface and many other changes under the hood. These key points should help you decide whether Paperless-ng is something you would prefer over Paperless:
* Interface: The new front end is the main interface for Paperless-ng, the old interface still exists but most customizations (such as thumbnails for the document list) have been removed.0
* Interface: The new front end is the main interface for paperless-ng, the old interface still exists but most customizations (such as thumbnails for the document list) have been removed.
* Encryption: Paperless-ng does not support GnuPG anymore, since storing your data on encrypted file systems (that you optionally mount on demand) achieves about the same result.
* Resource usage: Paperless-ng does use a bit more resources than Paperless. Running the web server requires about 300MB of RAM or more, depending on the configuration. While adding documents, it requires about 300MB additional RAM, depending on the document. It still runs on Raspberry Pi (many users do that), but it has been generally geared to better use the resources of more powerful systems.
* Resource usage: Paperless-ng does use a bit more resources than Paperless. Running the web server requires about 300MB of RAM or more, depending on the configuration. While adding documents, it requires about 300MB additional RAM, depending on the document. It still runs on Pi (many users do that), but it has been generally geared to better use the resources of more powerful systems.
* API changes: If you rely on the REST API of paperless, some of its functionality has been changed.
For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation, especially the section about the [0.9.0 release](https://paperless-ng.readthedocs.io/en/latest/changelog.html#paperless-ng-0-9-0).
For a detailed list of changes, have a look at the [change log](https://paperless-ng.readthedocs.io/en/latest/changelog.html) in the documentation.
# How it Works
@@ -29,7 +25,7 @@ Paperless does not control your scanner, it only helps you deal with what your s
1. Buy a document scanner that can write to a place on your network. If you need some inspiration, have a look at the [scanner recommendations](https://paperless-ng.readthedocs.io/en/latest/scanners.html) page. Set it up to "scan to FTP" or something similar. It should be able to push scanned images to a server without you having to do anything. Of course if your scanner doesn't know how to automatically upload the file somewhere, you can always do that manually. Paperless doesn't care how the documents get into its local consumption directory.
- Alternatively, you can use any of the mobile scanning apps out there. We have an app that allows you to share documents with paperless, if you're on Android. See the section on affiliated projects below.
- Alternatively, you can use any of the mobile scanning apps out there. We have an app that allows you to share documents with paperless, if you're on Android. See the section on affiliated projects.
2. Wait for paperless to process your files. OCR is expensive, and depending on the power of your machine, this might take a bit of time.
3. Use the web frontend to sift through the database and find what you want.
@@ -39,8 +35,6 @@ Here's what you get:
![Dashboard](https://github.com/jonaswinkler/paperless-ng/raw/master/docs/_static/screenshots/dashboard.png)
If you want to see paperless-ng in action, [more screenshots are available in the documentation](https://paperless-ng.readthedocs.io/en/latest/screenshots.html).
# Features
* Performs OCR on your documents, adds selectable text to image only documents and adds tags, correspondents and document types to your documents.
@@ -64,17 +58,19 @@ If you want to see paperless-ng in action, [more screenshots are available in th
* Optimized for multi core systems: Paperless-ng consumes multiple documents in parallel.
* The integrated sanity checker makes sure that your document archive is in good health.
If you want to see some screenshots of paperless-ng in action, [some are available in the documentation](https://paperless-ng.readthedocs.io/en/latest/screenshots.html).
# Getting started
The recommended way to deploy paperless is docker-compose. The files in the /docker/compose directory are configured to pull the image from Docker Hub.
The recommended way to deploy paperless is docker-compose. The files in the /docker/hub directory are configured to pull the image from Docker Hub.
Read the [documentation](https://paperless-ng.readthedocs.io/en/latest/setup.html#installation) on how to get started.
Alternatively, you can install the dependencies and setup apache and a database server yourself. The documenation has a step by step guide on how to do it. Consider giving the Ansible role a shot, this essentially automates the entire bare metal installation process.
Alternatively, you can install the dependencies and setup apache and a database server yourself. The documenation has a step by step guide on how to do it.
# Migrating from Paperless to Paperless-ng
# Migrating to paperless-ng
Read the section about [migration](https://paperless-ng.readthedocs.io/en/latest/setup.html#migration-to-paperless-ng) in the documentation. Its also entirely possible to go back to Paperless by reverting the database migrations.
Read the section about [migration](https://paperless-ng.readthedocs.io/en/latest/setup.html#migration-to-paperless-ng) in the documentation. Its also entirely possible to go back to paperless by reverting the database migrations.
# Documentation
@@ -82,7 +78,9 @@ The documentation for Paperless-ng is available on [ReadTheDocs](https://paperle
# Translation
Paperless is available in many different languages. Translation is coordinated at crowdin. If you want to help out by translating paperless into your language, please head over to https://github.com/jonaswinkler/paperless-ng/issues/212 for details!
Paperless is currently available in English, German, Dutch, French, Portuguese, Italian, and Romanian.
There's an active translation project at crowdin! If you want to help out by translating paperless into your language, please head over to https://github.com/jonaswinkler/paperless-ng/issues/212 for details.
# Feature Requests
@@ -96,7 +94,7 @@ For bugs please [open an issue](https://github.com/jonaswinkler/paperless-ng/iss
There's still lots of things to be done, just have a look at open issues & discussions. If you feel like contributing to the project, please do! Bug fixes and improvements to the front end (I just can't seem to get some of these CSS things right) are always welcome. The documentation has some basic information on how to get started.
If you want to implement something big: Please start a discussion about that! Maybe I've already had something similar in mind and we can make it happen together. However, keep in mind that the general roadmap is to make the existing features stable and get them tested.
If you want to implement something big: Please start a discussion about that in the issues! Maybe I've already had something similar in mind and we can make it happen together. However, keep in mind that the general roadmap is to make the existing features stable and get them tested. See the roadmap above.
# Affiliated Projects
@@ -104,7 +102,6 @@ Paperless has been around a while now, and people are starting to build stuff on
* [Paperless App](https://github.com/bauerj/paperless_app): An Android/iOS app for Paperless. Updated to work with paperless-ng.
* [Paperless Share](https://github.com/qcasey/paperless_share). Share any files from your Android application with paperless. Very simple, but works with all of the mobile scanning apps out there that allow you to share scanned documents.
* [Scan to Paperless](https://github.com/sbrunner/scan-to-paperless): Scan and prepare (crop, deskew, OCR, ...) your documents for Paperless.
These projects also exist, but their status and compatibility with paperless-ng is unknown.
+8 -10
View File
@@ -12,29 +12,27 @@ Note that this role requires root access, so either run it in a playbook with a
- hosts: all
roles:
- role: paperless-ng
- role: ansible
become: yes
Role Variables
--------------
Most configuration variables from paperless-ng itself are available and accept their respective arguments.
Every `PAPERLESS_*` configuration variable is lowercased and instead prefixed with `paperlessng_*` in `defaults/main.yml`.
Every `PAPERLESS_*` configuration varaible is lowercased and instead prefixed with `paperlessng_*` in `defaults/main.yml`.
For a full listing including explanations and allowed values, see the current [documentation](https://paperless-ng.readthedocs.io/en/latest/configuration.html).
For a full listing including explainations and allowed values, see the current [documentation](https://paperless-ng.readthedocs.io/en/ng-0.9.14/configuration.html).
Additional variables available in this role are listed below, along with default values:
paperlessng_version: latest
paperlessng_version: 0.9.14
The [release](https://github.com/jonaswinkler/paperless-ng/releases) archive version of paperless-ng to install.
`latest` stands for the latest release of paperless-ng.
To install a specific version of paperless-ng, use the tag name of the release, e. g. `ng-1.4.4`, or specify a branch or commit id.
paperlessng_redis_host: localhost
paperlessng_redis_port: 6379
Separate configuration values that combine into `PAPERLESS_REDIS`.
Seperate configuration values that combine into `PAPERLESS_REDIS`.
paperlessng_db_type: sqlite
@@ -98,11 +96,11 @@ Example Playbook
- hosts: all
become: yes
vars_files:
- vars/paperless-ng.yml
- vars/main.yml
roles:
- paperless-ng
- ansible
`vars/paperless-ng.yml`:
`vars/main.yml`:
paperlessng_media_root: /mnt/media/smbshare
+1 -1
View File
@@ -329,7 +329,7 @@
- regexp: PAPERLESS_TIKA_ENDPOINT
line: "PAPERLESS_TIKA_ENDPOINT={{ paperlessng_tika_endpoint }}"
- regexp: PAPERLESS_TIKA_GOTENBERG_ENDPOINT
line: "PAPERLESS_TIKA_GOTENBERG_ENDPOINT={{ paperlessng_tika_gotenberg_endpoint }}"
line: "PAPERLESS_TIKA_GOTENBERG_ENDPOINT={{ paperlessng_tika_endpoint }}"
# Software tweaks
- regexp: PAPERLESS_TIME_ZONE
line: "PAPERLESS_TIME_ZONE={{ paperlessng_time_zone }}"
+1 -1
View File
@@ -1 +1 @@
COMPOSE_PROJECT_NAME=paperless
COMPOSE_PROJECT_NAME=paperless
@@ -1,94 +0,0 @@
# docker-compose file for running paperless from the Docker Hub.
# This file contains everything paperless needs to run.
# Paperless supports amd64, arm and arm64 hardware.
#
# All compose files of paperless configure paperless 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 8010.
#
# In addition to that, this docker-compose file adds the following optional
# configurations:
#
# - Instead of SQLite (default), PostgreSQL is used as the database server.
#
# To install and update paperless with this file, do the following:
#
# - Open portainer Stacks list and click 'Add stack'
# - Paste the contents of this file and assign a name, e.g. 'Paperless'
# - Click 'Deploy the stack' and wait for it to be deployed
# - Open the list of containers, select paperless_webserver_1
# - Click 'Console' and then 'Connect' to open the command line inside the container
# - Run 'python3 manage.py createsuperuser' to create a user
# - Exit the console
#
# For more extensive installation and update instructions, refer to the
# documentation.
version: "3.4"
services:
broker:
image: redis:6.0
restart: unless-stopped
db:
image: postgres:13
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: paperless
POSTGRES_USER: paperless
POSTGRES_PASSWORD: paperless
webserver:
image: jonaswinkler/paperless-ng:latest
restart: unless-stopped
depends_on:
- db
- broker
ports:
- 8010:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000"]
interval: 30s
timeout: 10s
retries: 5
volumes:
- data:/usr/src/paperless/data
- media:/usr/src/paperless/media
- ./export:/usr/src/paperless/export
- ./consume:/usr/src/paperless/consume
environment:
PAPERLESS_REDIS: redis://broker:6379
PAPERLESS_DBHOST: db
# The UID and GID of the user used to run paperless in the container. Set this
# to your UID and GID on the host so that you have write access to the
# consumption directory.
USERMAP_UID: 1000
USERMAP_GID: 100
# Additional languages to install for text recognition, separated by a
# whitespace. Note that this is
# different from PAPERLESS_OCR_LANGUAGE (default=eng), which defines the
# language used for OCR.
# The container installs English, German, Italian, Spanish and French by
# default.
# See https://packages.debian.org/search?keywords=tesseract-ocr-&searchon=names&suite=buster
# for available languages.
#PAPERLESS_OCR_LANGUAGES: tur ces
# Adjust this key if you plan to make paperless available publicly. It should
# be a very long sequence of random characters. You don't need to remember it.
#PAPERLESS_SECRET_KEY: change-me
# Use this variable to set a timezone for the Paperless Docker containers. If not specified, defaults to UTC.
#PAPERLESS_TIME_ZONE: America/Los_Angeles
# The default language to use for OCR. Set this to the language most of your
# documents are written in.
#PAPERLESS_OCR_LANGUAGE: eng
volumes:
data:
media:
pgdata:
Executable → Regular
+91 -38
View File
@@ -4,37 +4,89 @@ set -e
# Source: https://github.com/sameersbn/docker-gitlab/
map_uidgid() {
USERMAP_ORIG_UID=$(id -u paperless)
USERMAP_ORIG_GID=$(id -g paperless)
USERMAP_NEW_UID=${USERMAP_UID:-$USERMAP_ORIG_UID}
USERMAP_NEW_GID=${USERMAP_GID:-${USERMAP_ORIG_GID:-$USERMAP_NEW_UID}}
if [[ ${USERMAP_NEW_UID} != "${USERMAP_ORIG_UID}" || ${USERMAP_NEW_GID} != "${USERMAP_ORIG_GID}" ]]; then
echo "Mapping UID and GID for paperless:paperless to $USERMAP_NEW_UID:$USERMAP_NEW_GID"
usermod -u "${USERMAP_NEW_UID}" paperless
groupmod -o -g "${USERMAP_NEW_GID}" paperless
USERMAP_ORIG_UID=$(id -u paperless)
USERMAP_ORIG_GID=$(id -g paperless)
USERMAP_NEW_UID=${USERMAP_UID:-$USERMAP_ORIG_UID}
USERMAP_NEW_GID=${USERMAP_GID:-${USERMAP_ORIG_GID:-$USERMAP_NEW_UID}}
if [[ ${USERMAP_NEW_UID} != "${USERMAP_ORIG_UID}" || ${USERMAP_NEW_GID} != "${USERMAP_ORIG_GID}" ]]; then
echo "Mapping UID and GID for paperless:paperless to $USERMAP_NEW_UID:$USERMAP_NEW_GID"
usermod -u "${USERMAP_NEW_UID}" paperless
groupmod -o -g "${USERMAP_NEW_GID}" paperless
fi
}
wait_for_postgres() {
attempt_num=1
max_attempts=5
echo "Waiting for PostgreSQL to start..."
host="${PAPERLESS_DBHOST}"
port="${PAPERLESS_DBPORT}"
if [[ -z $port ]] ;
then
port="5432"
fi
while !</dev/tcp/$host/$port ;
do
if [ $attempt_num -eq $max_attempts ]
then
echo "Unable to connect to database."
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(expr "$attempt_num" + 1)
sleep 5
done
}
migrations() {
if [[ -n "${PAPERLESS_DBHOST}" ]]
then
wait_for_postgres
fi
(
# flock is in place to prevent multiple containers from doing migrations
# simultaneously. This also ensures that the db is ready when the command
# of the current container starts.
flock 200
echo "Apply database migrations..."
sudo -HEu paperless python3 manage.py migrate
) 200>/usr/src/paperless/data/migration_lock
}
initialize() {
map_uidgid
for dir in export data data/index media media/documents media/documents/originals media/documents/thumbnails; do
if [[ ! -d "../$dir" ]]; then
echo "Creating directory ../$dir"
if [[ ! -d "../$dir" ]]
then
echo "creating directory ../$dir"
mkdir ../$dir
fi
done
echo "Creating directory /tmp/paperless"
echo "creating directory /tmp/paperless"
mkdir -p /tmp/paperless
set +e
echo "Adjusting permissions of paperless files. This may take a while."
chown -R paperless:paperless ../
chown -R paperless:paperless /tmp/paperless
find .. -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} +
set -e
gosu paperless /sbin/docker-prepare.sh
migrations
}
install_languages() {
@@ -50,43 +102,44 @@ install_languages() {
apt-get update
for lang in "${langs[@]}"; do
pkg="tesseract-ocr-$lang"
# English is installed by default
#if [[ "$lang" == "eng" ]]; then
# continue
#fi
pkg="tesseract-ocr-$lang"
# English is installed by default
#if [[ "$lang" == "eng" ]]; then
# continue
#fi
if dpkg -s $pkg &>/dev/null; then
echo "Package $pkg already installed!"
continue
fi
if dpkg -s $pkg &> /dev/null; then
echo "package $pkg already installed!"
continue
fi
if ! apt-cache show $pkg &>/dev/null; then
echo "Package $pkg not found! :("
continue
fi
if ! apt-cache show $pkg &> /dev/null; then
echo "package $pkg not found! :("
continue
fi
echo "Installing package $pkg..."
if ! apt-get -y install "$pkg" &>/dev/null; then
echo "Could not install $pkg"
exit 1
fi
done
echo "Installing package $pkg..."
if ! apt-get -y install "$pkg" &> /dev/null; then
echo "Could not install $pkg"
exit 1
fi
done
}
echo "Paperless-ng docker container starting..."
# Install additional languages if specified
if [[ ! -z "$PAPERLESS_OCR_LANGUAGES" ]]; then
install_languages "$PAPERLESS_OCR_LANGUAGES"
if [[ ! -z "$PAPERLESS_OCR_LANGUAGES" ]]; then
install_languages "$PAPERLESS_OCR_LANGUAGES"
fi
initialize
if [[ "$1" != "/"* ]]; then
echo Executing management command "$@"
exec gosu paperless python3 manage.py "$@"
exec sudo -HEu paperless python3 manage.py "$@"
else
echo Executing "$@"
exec "$@"
fi
-72
View File
@@ -1,72 +0,0 @@
#!/usr/bin/env bash
wait_for_postgres() {
attempt_num=1
max_attempts=5
echo "Waiting for PostgreSQL to start..."
host="${PAPERLESS_DBHOST}"
port="${PAPERLESS_DBPORT}"
if [[ -z $port ]]; then
port="5432"
fi
while ! </dev/tcp/$host/$port; do
if [ $attempt_num -eq $max_attempts ]; then
echo "Unable to connect to database."
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(expr "$attempt_num" + 1)
sleep 5
done
}
migrations() {
(
# flock is in place to prevent multiple containers from doing migrations
# simultaneously. This also ensures that the db is ready when the command
# of the current container starts.
flock 200
echo "Apply database migrations..."
python3 manage.py migrate
) 200>/usr/src/paperless/data/migration_lock
}
search_index() {
index_version=1
index_version_file=/usr/src/paperless/data/.index_version
if [[ (! -f "$index_version_file") || $(<$index_version_file) != "$index_version" ]]; then
echo "Search index out of date. Updating..."
python3 manage.py document_index reindex
echo $index_version | tee $index_version_file >/dev/null
fi
}
superuser() {
if [[ -n "${PAPERLESS_ADMIN_USER}" ]]; then
python3 manage.py manage_superuser
fi
}
do_work() {
if [[ -n "${PAPERLESS_DBHOST}" ]]; then
wait_for_postgres
fi
migrations
search_index
superuser
}
do_work
+1 -1
View File
@@ -1,4 +1,4 @@
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails document_sanity_checker manage_superuser;
for command in document_archiver document_exporter document_importer mail_fetcher document_create_classifier document_index document_renamer document_retagger document_thumbnails document_sanity_checker;
do
echo "installing $command..."
sed "s/management_command/$command/g" management_script.sh > /usr/local/bin/$command
Executable → Regular
+1 -1
View File
@@ -6,7 +6,7 @@ cd /usr/src/paperless/src/
if [[ $(id -u) == 0 ]] ;
then
gosu paperless python3 manage.py management_command "$@"
sudo -HEu paperless python3 manage.py management_command "$@"
elif [[ $(id -un) == "paperless" ]] ;
then
python3 manage.py management_command "$@"
+12 -9
View File
@@ -149,15 +149,22 @@ Ansible Route
Most of the update process is automated when using the ansible role.
1. Update the role to the target release tag to make sure the ansible scripts are compatible:
1. Backup your defined role variables file outside the paperless source-tree:
.. code:: shell-session
$ ansible-galaxy install git+https://github.com/jonaswinkler/paperless-ng.git,master --force
$ cp ansible/vars.yml ~/vars.yml.old
2. Update the role variable definitions ``vars/paperless-ng.yml`` (where appropriate).
2. Pull the release tag you want to update to:
3. Run the ansible playbook you created created during :ref:`installation <setup-ansible>` again:
.. code:: shell-session
$ git fetch --all
$ git checkout ng-0.9.14
3. Update the role variable definitions ``ansible/vars.yml`` (where appropriate).
4. Run the ansible playbook you created created during :ref:`installation <setup-ansible>` again:
.. note::
@@ -186,9 +193,7 @@ This table lists the compatible versions for each database migration number.
+------------------+-----------------+
| 1012 | 1.1.0 - 1.2.1 |
+------------------+-----------------+
| 1014 | 1.3.0 - 1.3.1 |
+------------------+-----------------+
| 1016 | 1.3.2 - current |
| 1014 | 1.3.0 - current |
+------------------+-----------------+
Execute the following management command to migrate your database:
@@ -240,8 +245,6 @@ Document exporter
The document exporter exports all your data from paperless into a folder for
backup or migration to another DMS.
If you use the document exporter within a cronjob to backup your data you might use the ``-T`` flag behind exec to suppress "The input device is not a TTY" errors. For example: ``docker-compose exec -T webserver document_exporter ../export``
.. code::
document_exporter target [-c] [-f] [-d]
+8 -7
View File
@@ -10,13 +10,14 @@ easier.
Matching tags, correspondents and document types
################################################
Paperless will compare the matching algorithms defined by every tag and
correspondent already set in your database to see if they apply to the text in
a document. In other words, if you defined a tag called ``Home Utility``
that had a ``match`` property of ``bc hydro`` and a ``matching_algorithm`` of
``literal``, Paperless will automatically tag your newly-consumed document with
your ``Home Utility`` tag so long as the text ``bc hydro`` appears in the body
of the document somewhere.
After the consumer has tried to figure out what it could from the file name,
it starts looking at the content of the document itself. It will compare the
matching algorithms defined by every tag and correspondent already set in your
database to see if they apply to the text in that document. In other words,
if you defined a tag called ``Home Utility`` that had a ``match`` property of
``bc hydro`` and a ``matching_algorithm`` of ``literal``, Paperless will
automatically tag your newly-consumed document with your ``Home Utility`` tag
so long as the text ``bc hydro`` appears in the body of the document somewhere.
The matching logic is quite powerful, and supports searching the text of your
document with different algorithms, and as such, some experimentation may be
+74 -38
View File
@@ -147,57 +147,93 @@ The REST api provides three different forms of authentication.
Searching for documents
#######################
Full text searching is available on the ``/api/documents/`` endpoint. Two specific
query parameters cause the API to return full text search results:
Paperless-ng offers API endpoints for full text search. These are as follows:
* ``/api/documents/?query=your%20search%20query``: Search for a document using a full text query.
For details on the syntax, see :ref:`basic-usage_searching`.
``/api/search/``
================
* ``/api/documents/?more_like=1234``: Search for documents similar to the document with id 1234.
Get search results based on a query.
Pagination works exactly the same as it does for normal requests on this endpoint.
Query parameters:
Certain limitations apply to full text queries:
* ``query``: The query string. See
`here <https://whoosh.readthedocs.io/en/latest/querylang.html>`_
for details on the syntax.
* ``page``: Specify the page you want to retrieve. Each page
contains 10 search results and the first page is ``page=1``, which
is the default if this is omitted.
* Results are always sorted by search score. The results matching the query best will show up first.
Result list object returned by the endpoint:
* Only a small subset of filtering parameters are supported.
Furthermore, each returned document has an additional ``__search_hit__`` attribute with various information
about the search results:
.. code::
.. code:: json
{
"count": 31,
"next": "http://localhost:8000/api/documents/?page=2&query=test",
"previous": null,
"count": 1,
"page": 1,
"page_count": 1,
"corrected_query": "",
"results": [
...
{
"id": 123,
"title": "title",
"content": "content",
...
"__search_hit__": {
"score": 0.343,
"highlights": "text <span class=\"match\">Test</span> text",
"rank": 23
}
},
...
]
}
* ``score`` is an indication how well this document matches the query relative to the other search results.
* ``highlights`` is an excerpt from the document content and highlights the search terms with ``<span>`` tags as shown above.
* ``rank`` is the index of the search results. The first result will have rank 0.
* ``count``: The approximate total number of results.
* ``page``: The page returned to you. This might be different from
the page you requested, if you requested a page that is behind
the last page. In that case, the last page is returned.
* ``page_count``: The total number of pages.
* ``corrected_query``: Corrected version of the query string. Can be null.
If not null, can be used verbatim to start a new query.
* ``results``: A list of result objects on the current page.
Result object:
.. code:: json
{
"id": 1,
"highlights": [
],
"score": 6.34234,
"rank": 23,
"document": {
}
}
* ``id``: the primary key of the found document
* ``highlights``: an object containing parsable highlights for the result.
See below.
* ``score``: The score assigned to the document. A higher score indicates a
better match with the query. Search results are sorted descending by score.
* ``rank``: the position of the document within the entire search results list.
* ``document``: The full json of the document, as returned by
``/api/documents/<id>/``.
Highlights object:
Highlights are provided as a list of fragments. A fragment is a longer section of
text from the original document.
Each fragment contains a list of strings, and some of them are marked as a highlight.
.. code:: json
[
[
{"text": "This is a sample text with a ", "highlight": false},
{"text": "highlighted", "highlight": true},
{"text": " word.", "highlight": false}
],
[
{"text": "Another", "highlight": true},
{"text": " fragment with a highlight.", "highlight": false}
]
]
A client may use this example to produce the following output:
... This is a sample text with a **highlighted** word. ... **Another** fragment with a highlight. ...
``/api/search/autocomplete/``
=============================
+11 -110
View File
@@ -5,110 +5,6 @@
Changelog
*********
paperless-ng 1.5.0
##################
Support for Python 3.6 was dropped.
* Updated python dependencies.
* Base image of the docker image changed from Debian Buster to Debian Bullseye due to its recent release.
* The docker image now uses python 3.9.
* Added the Luxembourgish locale. Thanks for translating!
* `Daniel Albers`_ added support for making the files and folders ignored by the paperless consume folder scanner configurable. See ``PAPERLESS_CONSUMER_IGNORE_PATTERNS``.
paperless-ng 1.4.5
##################
This is a maintenance release.
* Updated Python and Angular dependencies.
* Changed the algorithm that changes permissions during startup. This is still fast,
and will hopefully cause less issues.
* Fixed an issue that would sometimes cause paperless to write an incomplete
classification model file to disk.
* Fixed an issue with the OCRmyPDF parser that would always try to extract text
with PDFminer even from non-PDF files.
paperless-ng 1.4.4
##################
* Drastically decreased the startup time of the docker container. The startup script adjusts file permissions of all data only if changes are required.
* Paperless mail: Added ability to specify the character set for each server.
* Document consumption: Ignore Mac OS specific files such as ``.DS_STORE`` and ``._XXXXX.pdf``.
* Fixed an issue with the automatic matching algorithm that prevents paperless from consuming new files.
* Updated translations.
paperless-ng 1.4.3
##################
* Additions and changes
* Added Swedish locale.
* `Stéphane Brunner`_ added an option to disable the progress bars of all management commands.
* `Jo Vandeginste`_ added support for RTF documents to the Apache TIKA parser.
* `Michael Shamoon`_ added dark mode for the login and logout pages.
* `Alexander Menk`_ added additional stylesheets for printing. You can now print any page of paperless and the print result will hide the page header, sidebar, and action buttons.
* Added support for sorting when using full text search.
* Fixes
* `puuu`_ fixed ``PAPERLESS_FORCE_SCRIPT_NAME``. You can now host paperless on sub paths such as ``https://localhost:8000/paperless/``.
* Fixed an issue with the document consumer crashing on certain documents due to issues with pdfminer.six. This library is used for PDF text extraction.
paperless-ng 1.4.2
##################
* Fixed an issue with ``sudo`` that caused paperless to not start on many Raspberry Pi devices. Thank you `WhiteHatTux`_!
paperless-ng 1.4.1
##################
* Added Polish locale.
* Changed some parts of the Dockerfile to hopefully restore functionality on certain ARM devices.
* Updated python dependencies.
* `Michael Shamoon`_ added a sticky filter / bulk edit bar.
* `sbrl`_ changed the docker-entrypoint.sh script to increase compatibility with NFS shares.
* `Chris Nagy`_ added support for creating a super user by passing ``PAPERLESS_ADMIN_USER`` and
``PAPERLESS_ADMIN_PASSWORD`` as environment variables to the docker container.
paperless-ng 1.4.0
##################
* Docker images now use tesseract 4.1.1, which should fix a series of issues with OCR.
* The full text search now displays results using the default document list. This enables
selection, filtering and bulk edit on search results.
* Changes
* Firefox only: Highlight search query in PDF previews.
* New URL pattern for accessing documents by ASN directly (http://<paperless>/asn/123)
* Added logging when executing pre- and post-consume scripts.
* Better error logging during document consumption.
* Updated python dependencies.
* Automatically inserts typed text when opening "Create new" dialogs on the document details page.
* Fixes
* Fixed an issue with null characters in the document content.
.. note::
The changed to the full text searching require you to reindex your documents.
*The docker image does this automatically, you don't need to do anything.*
To do this, execute the ``document_index reindex`` management command
(see :ref:`administration-index`).
paperless-ng 1.3.2
##################
@@ -212,6 +108,17 @@ paperless-ng 1.2.0
* Paperless no longer depends on ``libpoppler-cpp-dev``.
.. note::
Some packages that paperless depends on are slowly dropping Python 3.6
support one after another, including the web server. Supporting Python
3.6 means that I cannot update these packages anymore.
At some point, paperless will drop Python 3.6 support. If using a bare
metal installation and you're still on Python 3.6, upgrade to 3.7 or newer.
If using docker, this does not affect you.
paperless-ng 1.1.4
##################
@@ -1405,11 +1312,6 @@ bulk of the work on this big change.
* Initial release
.. _Alexander Menk: https://github.com/amenk
.. _puuu: https://github.com/puuu
.. _WhiteHatTux: https://github.com/WhiteHatTux
.. _Chris Nagy: https://github.com/what-name
.. _sbrl: https://github.com/sbrl
.. _slorenz: https://github.com/sisao
.. _Jo Vandeginste: https://github.com/jovandeginste
.. _zjean: https://github.com/zjean
@@ -1482,7 +1384,6 @@ bulk of the work on this big change.
.. _JOKer: https://github.com/MasterofJOKers
.. _Brian Cribbs: https://github.com/cribbstechnolog
.. _Brendan M. Sleight: https://github.com/bmsleight
.. _Daniel Albers: https://github.com/AlD
.. _#20: https://github.com/the-paperless-project/paperless/issues/20
.. _#44: https://github.com/the-paperless-project/paperless/issues/44
+10 -53
View File
@@ -154,6 +154,10 @@ PAPERLESS_FORCE_SCRIPT_NAME=<path>
To host paperless under a subpath url like example.com/paperless you set
this value to /paperless. No trailing slash!
.. note::
I don't know if this works in paperless-ng. Probably not.
Defaults to none, which hosts paperless at "/".
PAPERLESS_STATIC_URL=<path>
@@ -173,30 +177,6 @@ PAPERLESS_AUTO_LOGIN_USERNAME=<username>
Defaults to none, which disables this feature.
PAPERLESS_ADMIN_USER=<username>
If this environment variable is specified, Paperless automatically creates
a superuser with the provided username at start. This is useful in cases
where you can not run the `createsuperuser` command seperately, such as Kubernetes
or AWS ECS.
Requires `PAPERLESS_ADMIN_PASSWORD` to be set.
.. note::
This will not change an existing [super]user's password, nor will
it recreate a user that already exists. You can leave this throughout
the lifecycle of the containers.
PAPERLESS_ADMIN_MAIL=<email>
(Optional) Specify superuser email address. Only used when
`PAPERLESS_ADMIN_USER` is set.
Defaults to ``root@localhost``.
PAPERLESS_ADMIN_PASSWORD=<password>
Only used when `PAPERLESS_ADMIN_USER` is set.
This will be the password of the automatically created superuser.
PAPERLESS_COOKIE_PREFIX=<str>
Specify a prefix that is added to the cookies used by paperless to identify
@@ -222,24 +202,17 @@ PAPERLESS_ENABLE_HTTP_REMOTE_USER=<bool>
Also see the warning `in the official documentation <https://docs.djangoproject.com/en/3.1/howto/auth-remote-user/#configuration>`.
Defaults to `false` which disables this feature.
PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME=<str>
If `PAPERLESS_ENABLE_HTTP_REMOTE_USER` is enabled, this property allows to
customize the name of the HTTP header from which the authenticated username
If `PAPERLESS_ENABLE_HTTP_REMOTE_USER` is enabled, this property allows to
customize the name of the HTTP header from which the authenticated username
is extracted. Values are in terms of
[HttpRequest.META](https://docs.djangoproject.com/en/3.1/ref/request-response/#django.http.HttpRequest.META).
Thus, the configured value must start with `HTTP_` followed by the
Thus, the configured value must start with `HTTP_` followed by the
normalized actual header name.
Defaults to `HTTP_REMOTE_USER`.
PAPERLESS_LOGOUT_REDIRECT_URL=<str>
URL to redirect the user to after a logout. This can be used together with
`PAPERLESS_ENABLE_HTTP_REMOTE_USER` to redirect the user back to the SSO
application's logout page.
Defaults to None, which disables this feature.
.. _configuration-ocr:
OCR settings
@@ -265,8 +238,6 @@ PAPERLESS_OCR_LANGUAGE=<lang>
Defaults to "eng".
Note: If your language contains a '-' such as chi-sim, you must use chi_sim
PAPERLESS_OCR_MODE=<mode>
Tell paperless when and how to perform ocr on your documents. Four modes
are available:
@@ -631,20 +602,6 @@ PAPERLESS_IGNORE_DATES=<string>
Defaults to an empty string to not ignore any dates.
PAPERLESS_DATE_ORDER=<format>
Paperless will try to determine the document creation date from its contents.
Specify the date format Paperless should expect to see within your documents.
This option defaults to DMY which translates to day first, month second, and year
last order. Characters D, M, or Y can be shuffled to meet the required order.
PAPERLESS_CONSUMER_IGNORE_PATTERNS=<json>
By default, paperless ignores certain files and folders in the consumption
directory, such as system files created by the Mac OS.
This can be adjusted by configuring a custom json array with patterns to exclude.
Defautls to ``[".DS_STORE/*", "._*", ".stfolder/*"]``.
Binaries
########
+1 -1
View File
@@ -6,7 +6,7 @@ Contributing to Paperless
.. warning::
This section is not updated to paperless-ng yet.
Maybe you've been using Paperless for a while and want to add a feature or two,
or maybe you've come across a bug that you have some ideas how to solve. The
beauty of Free software is that you can see what's wrong and help to get it
+9 -9
View File
@@ -23,10 +23,10 @@ Apart from that, the folder structure is as follows:
* ``scripts/`` - Various scripts that help with different parts of development.
* ``docker/`` - Files required to build the docker image.
Initial setup and first start
Initial setup and first start
=============================
After you forked and cloned the code from github you need to perform a first-time setup.
After you forked and cloned the code from github you need to perform a first-time setup.
To do the setup you need to perform the steps from the following chapters in a certain order:
1. Install prerequisites + pipenv as mentioned in :ref:`Bare metal route <setup-bare_metal>`
@@ -35,19 +35,19 @@ To do the setup you need to perform the steps from the following chapters in a c
.. code:: shell-session
$ npm install -g @angular/cli
$ npm install -g @angular/cli
4. Create ``consume`` and ``media`` folders in the cloned root folder.
.. code:: shell-session
mkdir -p consume media
5. You can now either ...
* install redis or
* install redis or
* use the included scripts/start-services.sh to use docker to fire up a redis instance (and some other services such as tika, gotenberg and a postgresql server) or
* spin up a bare redis container
* spin up a bare redis container
.. code:: shell-session
@@ -79,7 +79,7 @@ To do the setup you need to perform the steps from the following chapters in a c
python3 manage.py runserver & python3 manage.py document_consumer & python3 manage.py qcluster
10. Login with the superuser credentials provided in step 8 at ``http://localhost:8000`` to create a session that enables you to use the backend.
Backend development environment is now ready, to start Frontend development go to ``/src-ui`` and run ``ng serve``. From there you can use ``http://localhost:4200`` for a preview.
Back end development
@@ -207,7 +207,7 @@ Adding new languages requires adding the translated files in the "src-ui/src/loc
// Add your new language here
]
}
``dateInputFormat`` is a special string that defines the behavior of the date input fields and absolutely needs to contain "dd", "mm" and "yyyy".
3. Import and register the Angular data for this locale in "src-ui/src/app/app.module.ts":
@@ -320,7 +320,7 @@ methods ``parse`` and ``get_thumbnail``. You can provide your own implementation
# The content of the document.
self.text = "content"
# Optional: path to a PDF document that you created from the original.
self.archive_path = os.path.join(self.tempdir, "archived.pdf")
+2 -2
View File
@@ -68,10 +68,10 @@ reuse the text. The web interface is a lot snappier, since it runs
in your browser and paperless has to do much less work to serve the data.
.. note::
You can adjust some of the settings so that paperless uses less processing
power. See :ref:`setup-less_powerful_devices` for details.
**Q:** *How do I install paperless-ng on Raspberry Pi?*
+1 -1
View File
@@ -49,7 +49,7 @@ resources in the documentation:
paperless-ng.
* Paperless is now integrated with a
:ref:`task processing queue <setup-task_processor>` that tells you
at a glance when and why something is not working.
at a glance when and why something is not working.
* The :ref:`changelog <paperless_changelog>` contains a detailed list of all changes
in paperless-ng.
+29 -57
View File
@@ -13,49 +13,33 @@ that works right for you based on recommendations from other Paperless users.
Physical scanners
=================
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brand | Model | Supports | Recommended By |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| | | FTP | NFS | SMB | SMTP | API [1]_ | |
+=========+================+=====+=====+=====+======+==========+================+
| Brother | `ADS-1700W`_ | yes | no | yes | yes | |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-1600W`_ | yes | no | yes | yes | |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-1500W`_ | yes | no | yes | yes | |`danielquinn`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `ADS-1100W`_ | yes | no | no | no | |`ytzelf`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-J6930DW`_ | yes | | | | |`ayounggun`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-L5850DW`_ | yes | | | yes | |`holzhannes`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-J5910DW`_ | yes | | | | |`bmsleight`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Brother | `MFC-9142CDN`_ | yes | | yes | | |`REOLDEV`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Fujitsu | `ix500`_ | yes | | yes | | |`eonist`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Epson | `WF-7710DWF`_ | yes | | yes | | |`Skylinar`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Fujitsu | `S1300i`_ | yes | | yes | | |`jonaswinkler`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
| Doxie | `Q2`_ | no | no | no | no | yes |`Unkn0wnCat`_ |
+---------+----------------+-----+-----+-----+------+----------+----------------+
+---------+----------------+-----+-----+-----+----------------+
| Brand | Model | Supports | Recommended By |
+---------+----------------+-----+-----+-----+----------------+
| | | FTP | NFS | SMB | |
+=========+================+=====+=====+=====+================+
| Brother | `ADS-1500W`_ | yes | no | yes | `danielquinn`_ |
+---------+----------------+-----+-----+-----+----------------+
| Brother | `MFC-J6930DW`_ | yes | | | `ayounggun`_ |
+---------+----------------+-----+-----+-----+----------------+
| Brother | `MFC-J5910DW`_ | yes | | | `bmsleight`_ |
+---------+----------------+-----+-----+-----+----------------+
| Brother | `MFC-9142CDN`_ | yes | | yes | `REOLDEV`_ |
+---------+----------------+-----+-----+-----+----------------+
| Fujitsu | `ix500`_ | yes | | yes | `eonist`_ |
+---------+----------------+-----+-----+-----+----------------+
| Epson | `WF-7710DWF`_ | yes | | yes | `Skylinar`_ |
+---------+----------------+-----+-----+-----+----------------+
| Fujitsu | `S1300i`_ | yes | | yes | `jonaswinkler`_|
+---------+----------------+-----+-----+-----+----------------+
.. _MFC-L5850DW: https://www.brother-usa.com/products/mfcl5850dw
.. _ADS-1700W: https://www.brother-usa.com/products/ads1700w
.. _ADS-1600W: https://www.brother-usa.com/products/ads1600w
.. _ADS-1500W: https://www.brother.ca/en/p/ads1500w
.. _ADS-1100W: https://support.brother.com/g/b/downloadtop.aspx?c=fr&lang=fr&prod=ads1100w_eu_as_cn
.. _MFC-J6930DW: https://www.brother.ca/en/p/MFCJ6930DW
.. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw
.. _MFC-9142CDN: https://www.brother.co.uk/printers/laser-printers/mfc9140cdn
.. _ix500: http://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/ix500/
.. _WF-7710DWF: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf
.. _S1300i: https://www.fujitsu.com/global/products/computing/peripheral/scanners/soho/s1300i/
.. _Q2: https://www.getdoxie.com/product/doxie-q/
.. _danielquinn: https://github.com/danielquinn
.. _ayounggun: https://github.com/ayounggun
@@ -64,37 +48,25 @@ Physical scanners
.. _REOLDEV: https://github.com/REOLDEV
.. _Skylinar: https://github.com/Skylinar
.. _jonaswinkler: https://github.com/jonaswinkler
.. _holzhannes: https://github.com/holzhannes
.. _ytzelf: https://github.com/ytzelf
.. _Unkn0wnCat: https://github.com/Unkn0wnCat
.. [1] Scanners with API Integration allow to push scanned documents directly to :ref:`Paperless API <api-file_uploads>`, sometimes referred to as Webhook or Document POST.
Mobile phone software
=====================
You can use your phone to "scan" documents. The regular camera app will work, but may have too low contrast for OCR to work well. Apps specifically for scanning are recommended.
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| Name | OS | Supports | Recommended By |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| | | FTP | NFS | SMB | Email | WebDav | |
+===================+================+=====+=====+=====+=======+========+==================+
| `Office Lens`_ | Android | ? | ? | ? | ? | ? | `jonaswinkler`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| `Genius Scan`_ | Android | yes | no | yes | yes | yes | `hannahswain`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| `OpenScan`_ | Android | no | no | no | no | no | `benjaminfrank`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
| `Quick Scan`_ | iOS | no | no | no | no | no | `holzhannes`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+------------------+
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
| Name | OS | Supports | Recommended By |
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
| | | FTP | NFS | SMB | Email | WebDav | |
+===================+================+=====+=====+=====+=======+========+================+
| `Office Lens`_ | Android | ? | ? | ? | ? | ? | `jonaswinkler`_|
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
| `Genius Scan`_ | Android | yes | no | yes | yes | yes | `hannahswain`_ |
+-------------------+----------------+-----+-----+-----+-------+--------+----------------+
On Android, you can use these applications in combination with one of the :ref:`Paperless-ng compatible apps <usage-mobile_upload>` to "Share" the documents produced by these scanner apps with paperless. On iOS, you can share the scanned documents via iOS-Sharing to other mail, WebDav or FTP apps.
On Android, you can use these applications in combination with one of the :ref:`Paperless-ng compatible apps <usage-mobile_upload>` to "Share" the documents produced by these scanner apps with paperless.
.. _Office Lens: https://play.google.com/store/apps/details?id=com.microsoft.office.officelens
.. _Genius Scan: https://play.google.com/store/apps/details?id=com.thegrizzlylabs.geniusscan.free
.. _Quick Scan: https://apps.apple.com/us/app/quickscan-scanner-text-ocr/id1513790291
.. _OpenScan: https://github.com/Ethereal-Developers-Inc/OpenScan
.. _hannahswain: https://github.com/hannahswain
.. _benjaminfrank: https://github.com/benjaminfrank
+23 -37
View File
@@ -116,7 +116,9 @@ performs all the steps described in :ref:`setup-docker_hub` automatically.
.. code:: shell-session
$ curl -L https://raw.githubusercontent.com/jonaswinkler/paperless-ng/master/install-paperless-ng.sh | bash
$ wget https://raw.githubusercontent.com/jonaswinkler/paperless-ng/master/install-paperless-ng.sh
$ chmod +x install-paperless-ng.sh
$ ./install-paperless-ng.sh
.. _setup-docker_hub:
@@ -171,24 +173,6 @@ Install Paperless from Docker Hub
Don't change the part after the colon or paperless wont find your documents.
You may also need to change the default port that the webserver will use
from the default (8000):
.. code::
ports:
- 8000:8000
Replace the part BEFORE the colon with a port of your choice:
.. code::
ports:
- 8010:8000
Don't change the part after the colon or edit other lines that refer to
port 8000. Modifying the part before the colon will map requests on another
port to the webserver running on the default port.
5. Modify ``docker-compose.env``, following the comments in the file. The
most important change is to set ``USERMAP_UID`` and ``USERMAP_GID``
@@ -302,7 +286,7 @@ writing. Windows is not and will never be supported.
Use this list for your preferred package management:
.. code::
.. code::
python3 python3-pip python3-dev imagemagick fonts-liberation optipng gnupg libpq-dev libmagic-dev mime-support
@@ -321,7 +305,7 @@ writing. Windows is not and will never be supported.
Use this list for your preferred package management:
.. code::
.. code::
unpaper ghostscript icc-profiles-free qpdf liblept5 libxml2 pngquant zlib1g tesseract-ocr
@@ -376,11 +360,7 @@ writing. Windows is not and will never be supported.
Adjust as necessary if you configured different folders.
8. Install python requirements from the ``requirements.txt`` file.
It is up to you if you wish to use a virtual environment or not. First you should update your pip, so it gets the actual packages.
.. code:: shell-session
sudo -Hu paperless pip3 install --upgrade pip
It is up to you if you wish to use a virtual environment or not.
.. code:: shell-session
@@ -480,7 +460,6 @@ Install Paperless using ansible
.. note::
This role currently only supports Debian 10 Buster and Ubuntu 20.04 Focal or later as target hosts.
Additionally, only i386 or amd64 based hosts are supported right now, i.e. installation on arm hosts will fail.
1. Install ansible 2.7+ on the management node.
This may be the target host paperless-ng is being installed on or any remote host which can access the target host.
@@ -506,22 +485,29 @@ Install Paperless using ansible
ansible -m ping YourAnsibleTargetHostGoesHere
2. Install the latest tag of the ansible role using ansible-galaxy
2. Clone the repository of paperless-ng:
.. code:: sh
ansible-galaxy install git+https://github.com/jonaswinkler/paperless-ng.git,ng-1.4.2
git clone https://github.com/jonaswinkler/paperless-ng
3. Create an ansible ``playbook.yml`` in a directory of your choice:
Checkout the latest release tag:
.. code:: sh
cd paperless-ng
git checkout ng-1.0.0
3. Create an ansible ``playbook.yml`` in the paperless-ng root directory:
.. code:: yaml
- hosts: YourAnsibleTargetHostGoesHere
become: yes
vars_files:
- vars/paperless-ng.yml
- ansible/vars.yml
roles:
- paperless-ng
- ansible
Optional: If you also want to use PostgreSQL on the target system, install and add (for example) the `geerlingguy.postgresql <https://github.com/geerlingguy/ansible-role-postgresql>`_ role:
@@ -534,10 +520,10 @@ Install Paperless using ansible
- hosts: YourAnsibleTargetHostGoesHere
become: yes
vars_files:
- vars/paperless-ng.yml
- ansible/vars.yml
roles:
- geerlingguy.postgresql
- paperless-ng
- ansible
Optional: If you also want to use a reverse proxy on the target system, install and add (for example) the `geerlingguy.nginx <https://github.com/geerlingguy/ansible-role-nginx>`_ role:
@@ -550,13 +536,13 @@ Install Paperless using ansible
- hosts: YourAnsibleTargetHostGoesHere
become: yes
vars_files:
- vars/paperless-ng.yml
- ansible/vars.yml
roles:
- geerlingguy.postgresql
- paperless-ng
- ansible
- geerlingguy.nginx
4. Create ``vars/paperless-ng.yml`` to configure your ansible deployment:
4. Create ``ansible/vars.yml`` to configure your ansible deployment:
.. code:: yaml
+3 -16
View File
@@ -81,7 +81,7 @@ UserWarning in sklearn on every single document
You may encounter warnings like this:
.. code::
/usr/local/lib/python3.7/site-packages/sklearn/base.py:315:
UserWarning: Trying to unpickle estimator CountVectorizer from version 0.23.2 when using version 0.24.0.
This might lead to breaking code or invalid results. Use at your own risk.
@@ -200,24 +200,11 @@ This might have multiple reasons.
File "/usr/local/lib/python3.7/site-packages/gunicorn/http/wsgi.py", line 386, in sendfile
sent += os.sendfile(sockno, fileno, offset + sent, count)
OSError: [Errno 22] Invalid argument
To fix this issue, add
.. code::
SENDFILE=0
to your `docker-compose.env` file.
Error while reading metadata
############################
You might find messages like these in your log files:
.. code::
[WARNING] [paperless.parsing.tesseract] Error while reading metadata
This indicates that paperless failed to read PDF metadata from one of your documents. This happens when you
open the affected documents in paperless for editing. Paperless will continue to work, and will simply not
show the invalid metadata.
+5 -7
View File
@@ -71,7 +71,7 @@ your documents:
This process can be configured to fit your needs. If you don't want paperless
to create archived versions for digital documents, you can configure that by
configuring ``PAPERLESS_OCR_MODE=skip_noarchive``. Please read the
configuring ``PAPERLESS_OCR_MODE=skip_noarchive``. Please read the
:ref:`relevant section in the documentation <configuration-ocr>`.
.. note::
@@ -255,8 +255,6 @@ Here are a couple examples of tags and types that you could use in your collecti
* A tag ``missing_metadata`` when you still need to add some metadata to a document, but can't
or don't want to do this right now.
.. _basic-usage_searching:
Searching
#########
@@ -289,7 +287,7 @@ Matching specific tags, correspondents or types:
Matching dates:
.. code::
created:[2005 to 2009]
added:yesterday
modified:today
@@ -306,11 +304,11 @@ Matching inexact words:
auto complete and query correction.
All of these constructs can be combined as you see fit.
If you want to learn more about the query language used by paperless, paperless uses Whoosh's default query language.
If you want to learn more about the query language used by paperless, paperless uses Whoosh's default query language.
Head over to `Whoosh query language <https://whoosh.readthedocs.io/en/latest/querylang.html>`_.
For details on what date parsing utilities are available, see
`Date parsing <https://whoosh.readthedocs.io/en/latest/dates.html#parsing-date-queries>`_.
.. _usage-recommended_workflow:
@@ -385,7 +383,7 @@ Once you have scanned in a document, proceed in paperless as follows.
6. Remove inbox tags from the documents.
.. hint::
You can setup manual matching rules for your correspondents and tags and
paperless will assign them automatically. After consuming a couple documents,
you can even ask paperless to *learn* when to assign tags and correspondents
+1 -1
View File
@@ -2,7 +2,7 @@ import os
bind = '0.0.0.0:8000'
workers = int(os.getenv("PAPERLESS_WEBSERVER_WORKERS", 2))
worker_class = 'paperless.workers.ConfigurableWorker'
worker_class = 'uvicorn.workers.UvicornWorker'
timeout = 120
def pre_fork(server, worker):
+1 -13
View File
@@ -62,7 +62,7 @@ if [[ -z $(which docker-compose) ]] ; then
exit 1
fi
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
# Check if user has permissions to run Docker by trying to get the status of Docker (docker status).
# If this fails, the user probably does not have permissions for Docker.
docker stats --no-stream 2>/dev/null 1>&2
if [ $? -ne 0 ] ; then
@@ -73,8 +73,6 @@ if [ $? -ne 0 ] ; then
sleep 3
fi
default_time_zone=$(timedatectl show -p Timezone --value)
set -e
echo ""
@@ -147,15 +145,6 @@ echo ""
ask "Port" "8000"
PORT=$ask_result
echo ""
echo "Paperless requires you to configure the current time zone correctly."
echo "Otherwise, the dates of your documents may appear off by one day,"
echo "depending on where you are on earth."
echo ""
ask "Current time zone" "$default_time_zone"
TIME_ZONE=$ask_result
echo ""
echo "Database backend: PostgreSQL and SQLite are available. Use PostgreSQL"
echo "if unsure. If you're running on a low-power device such as Raspberry"
@@ -291,7 +280,6 @@ DEFAULT_LANGUAGES="deu eng fra ita spa"
if [[ ! $USERMAP_GID == "1000" ]] ; then
echo "USERMAP_GID=$USERMAP_GID"
fi
echo "PAPERLESS_TIME_ZONE=$TIME_ZONE"
echo "PAPERLESS_OCR_LANGUAGE=$OCR_LANGUAGE"
echo "PAPERLESS_SECRET_KEY=$SECRET_KEY"
if [[ ! " ${DEFAULT_LANGUAGES[@]} " =~ " ${OCR_LANGUAGE} " ]] ; then
-1
View File
@@ -57,7 +57,6 @@
#PAPERLESS_CONSUMER_POLLING=10
#PAPERLESS_CONSUMER_DELETE_DUPLICATES=false
#PAPERLESS_CONSUMER_RECURSIVE=false
#PAPERLESS_CONSUMER_IGNORE_PATTERNS=[".DS_STORE/*", "._*", ".stfolder/*"]
#PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=false
#PAPERLESS_OPTIMIZE_THUMBNAILS=true
#PAPERLESS_POST_CONSUME_SCRIPT=/path/to/an/arbitrary/script.sh
+54 -57
View File
@@ -8,96 +8,93 @@
-i https://pypi.python.org/simple
--extra-index-url https://www.piwheels.org/simple
aioredis==1.3.1
arrow==1.1.1; python_version >= '3.6'
asgiref==3.4.1; python_version >= '3.6'
arrow==1.0.3; python_version >= '3.6'
asgiref==3.3.1; python_version >= '3.5'
async-timeout==3.0.1; python_full_version >= '3.5.3'
attrs==21.2.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
attrs==20.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
autobahn==21.3.1; python_version >= '3.7'
automat==20.2.0
backports.zoneinfo==0.2.1
blessed==1.18.1; python_version >= '2.7'
certifi==2021.5.30
cffi==1.14.6
channels-redis==3.3.0
channels==3.0.4
blessed==1.18.0
certifi==2020.12.5
cffi==1.14.5
channels-redis==3.2.0
channels==3.0.3
chardet==4.0.0; python_version >= '3.1'
charset-normalizer==2.0.4; python_version >= '3'
click==8.0.1; python_version >= '3.6'
coloredlogs==15.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
click==7.1.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
coloredlogs==15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
concurrent-log-handler==0.9.19
constantly==15.1.0
cryptography==3.4.7
daphne==3.0.2; python_version >= '3.6'
cryptography==3.4.6
daphne==3.0.1; python_version >= '3.6'
dateparser==1.0.0
django-cors-headers==3.8.0
django-extensions==3.1.3
django-cors-headers==3.7.0
django-extensions==3.1.1
django-filter==2.4.0
django-picklefield==3.0.1; python_version >= '3'
django-q==1.3.9
django==3.2.6
djangorestframework==3.12.4
django-q==1.3.4
django==3.1.7
djangorestframework==3.12.2
filelock==3.0.12
fuzzywuzzy[speedup]==0.18.0
gunicorn==20.1.0
gunicorn==20.0.4
h11==0.12.0; python_version >= '3.6'
hiredis==2.0.0; python_version >= '3.6'
httptools==0.2.0
humanfriendly==9.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
hiredis==1.1.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
httptools==0.1.1
humanfriendly==9.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
hyperlink==21.0.0
idna==3.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
imap-tools==0.46.0
img2pdf==0.4.1
idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
imap-tools==0.39.0
img2pdf==0.4.0
incremental==21.3.0
inotify-simple==1.3.5; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
inotifyrecursive==0.3.5
joblib==1.0.1; python_version >= '3.6'
langdetect==1.0.9
lxml==4.6.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
langdetect==1.0.8
lxml==4.6.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
msgpack==1.0.2
numpy==1.20.3
ocrmypdf==12.3.2
pathvalidate==2.4.1
numpy==1.19.5
ocrmypdf==11.7.2
pathvalidate==2.3.2
pdfminer.six==20201018
pikepdf==2.16.1
pillow==8.3.1
pikepdf==2.9.1
pillow==8.1.2
pluggy==0.13.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
portalocker==2.3.0; python_version >= '3'
psycopg2-binary==2.9.1
portalocker==2.2.1; python_version >= '3'
psycopg2-binary==2.8.6
pyasn1-modules==0.2.8
pyasn1==0.4.8
pycparser==2.20; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
pyopenssl==20.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
python-dateutil==2.8.2
python-dotenv==0.19.0
python-dateutil==2.8.1
python-dotenv==0.15.0
python-gnupg==0.4.7
python-levenshtein==0.12.2
python-magic==0.4.24
python-magic==0.4.22
pytz==2021.1
pyyaml==5.4.1
redis==3.5.3
regex==2021.8.3
reportlab==3.6.1; python_version >= '2.7' and python_version < '4'
requests==2.26.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
regex==2021.3.17
reportlab==3.5.66; python_version >= '2.7' and python_version < '4'
requests==2.25.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
scikit-learn==0.24.0
scipy==1.7.1; python_version < '3.10' and python_version >= '3.7'
service-identity==21.1.0
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sortedcontainers==2.4.0
scipy==1.5.4
service-identity==18.1.0
six==1.15.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sortedcontainers==2.3.0
sqlparse==0.4.1; python_version >= '3.5'
threadpoolctl==2.2.0; python_version >= '3.6'
threadpoolctl==2.1.0; python_version >= '3.5'
tika==1.24
tqdm==4.62.1
twisted[tls]==21.7.0; python_full_version >= '3.6.7'
tqdm==4.59.0
twisted[tls]==21.2.0; python_full_version >= '3.5.4'
txaio==21.2.1; python_version >= '3.6'
typing-extensions==3.10.0.0
tzlocal==3.0; python_version >= '3.6'
urllib3==1.26.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
uvicorn[standard]==0.15.0
uvloop==0.16.0
watchdog==2.1.3
tzlocal==2.1
urllib3==1.26.4; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'
uvicorn[standard]==0.13.4
uvloop==0.14.0
watchdog==1.0.2
watchgod==0.7
wcwidth==0.2.5
websockets==9.1
whitenoise==5.3.0
websockets==8.1
whitenoise==5.2.0
whoosh==2.7.4
zope.interface==5.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
zope.interface==5.3.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit]
Description=Paperless scheduler
Description=Paperless consumer
Requires=redis.service
[Service]
+2 -4
View File
@@ -25,10 +25,7 @@
"it-IT": "src/locale/messages.it_IT.xlf",
"ro-RO": "src/locale/messages.ro_RO.xlf",
"ru-RU": "src/locale/messages.ru_RU.xlf",
"es-ES": "src/locale/messages.es_ES.xlf",
"pl-PL": "src/locale/messages.pl_PL.xlf",
"sv-SE": "src/locale/messages.sv_SE.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf"
"es-ES": "src/locale/messages.es_ES.xlf"
}
},
"architect": {
@@ -73,6 +70,7 @@
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
+1 -1
View File
@@ -33,4 +33,4 @@ exports.config = {
}
}));
}
};
};
+114 -151
View File
@@ -48,21 +48,21 @@
<source>Documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">51</context>
<context context-type="linenumber">49</context>
</context-group>
</trans-unit>
<trans-unit id="2155249406916744630" datatype="html">
<source>View &quot;<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>&quot; saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">116</context>
<context context-type="linenumber">115</context>
</context-group>
</trans-unit>
<trans-unit id="6837554170707123455" datatype="html">
<source>View &quot;<x id="PH" equiv-text="savedView.name"/>&quot; created successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
<context context-type="linenumber">138</context>
<context context-type="linenumber">136</context>
</context-group>
</trans-unit>
<trans-unit id="9ca82952a6bc860b5391d5975322d8af8ceddfa4" datatype="html">
@@ -146,77 +146,77 @@
<source>ASN</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">105</context>
</context-group>
</trans-unit>
<trans-unit id="7b5c6286aaded63fb279d6deb8aa8c704e085ced" datatype="html">
<source>Correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">117</context>
<context context-type="linenumber">111</context>
</context-group>
</trans-unit>
<trans-unit id="fdf7cbdc140d0aab0f0b6c06065a0fd448ed6a2e" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">123</context>
<context context-type="linenumber">117</context>
</context-group>
</trans-unit>
<trans-unit id="2bd5919e8098513664a89d5b7b52d61e3063950f" datatype="html">
<source>Document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">129</context>
<context context-type="linenumber">123</context>
</context-group>
</trans-unit>
<trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html">
<source>Created</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">135</context>
<context context-type="linenumber">129</context>
</context-group>
</trans-unit>
<trans-unit id="80e3b490720757978c99a7b5af3885faf202b955" datatype="html">
<source>Added</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
<context context-type="linenumber">141</context>
<context context-type="linenumber">135</context>
</context-group>
</trans-unit>
<trans-unit id="9021887951960049161" datatype="html">
<source>Confirm delete</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">206</context>
<context context-type="linenumber">203</context>
</context-group>
</trans-unit>
<trans-unit id="5382975254277698192" datatype="html">
<source>Do you really want to delete document &quot;<x id="PH" equiv-text="this.document.title"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">207</context>
<context context-type="linenumber">204</context>
</context-group>
</trans-unit>
<trans-unit id="6691075929777935948" datatype="html">
<source>The files for this document will be deleted permanently. This operation cannot be undone.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">208</context>
<context context-type="linenumber">205</context>
</context-group>
</trans-unit>
<trans-unit id="719892092227206532" datatype="html">
<source>Delete document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">210</context>
<context context-type="linenumber">207</context>
</context-group>
</trans-unit>
<trans-unit id="1844801255494293730" datatype="html">
<source>Error deleting document: <x id="PH" equiv-text="JSON.stringify(error)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
<context context-type="linenumber">217</context>
<context context-type="linenumber">214</context>
</context-group>
</trans-unit>
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" datatype="html">
@@ -912,6 +912,48 @@
<context context-type="linenumber">25</context>
</context-group>
</trans-unit>
<trans-unit id="49c9ede51100b454f7841b24cd02355c6622bf44" datatype="html">
<source>Search results</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="31976d04f98e8a38098f66ac3a83ad33b576e5db" datatype="html">
<source>Invalid search query: <x id="INTERPOLATION" equiv-text="{{errorMessage}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">4</context>
</context-group>
</trans-unit>
<trans-unit id="f7f2e30106223a69bcf0f8e586e6be9dc4e72226" datatype="html">
<source>Showing documents similar to <x id="START_LINK" equiv-text="&lt;a routerLink=&quot;/documents/{{more_like}}&quot;&gt;{{more_like_doc?.original_file_name}}"/><x id="INTERPOLATION" equiv-text="{{more_like_doc?.original_file_name}}&lt;/a&gt;"/><x id="CLOSE_LINK" equiv-text="&lt;/a&gt;"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">6</context>
</context-group>
</trans-unit>
<trans-unit id="6e0b0a1ea16f18f2fb1586c53d99d2f22e1aee2e" datatype="html">
<source>Search query: <x id="START_ITALIC_TEXT" equiv-text="&lt;i&gt;{{query}}"/><x id="INTERPOLATION" equiv-text="{{query}}&lt;/i&gt;"/><x id="CLOSE_ITALIC_TEXT" equiv-text="&lt;/i&gt;"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">9</context>
</context-group>
</trans-unit>
<trans-unit id="afa760e48c97d64d19c1455d18b7834a2256e23f" datatype="html">
<source>Did you mean &quot;<x id="START_LINK" equiv-text="&lt;a [routerLink]=&quot;&quot; (click)=&quot;searchCorrectedQuery()&quot;&gt;{{correctedQuery}}"/><x id="INTERPOLATION" equiv-text="{{correctedQuery}}&lt;/a&gt;"/><x id="CLOSE_LINK" equiv-text="&lt;/a&gt;"/>&quot;?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="fe6ced3fcc803bba5a2e6c1a067b9ce62542500e" datatype="html">
<source>{VAR_PLURAL, plural, =0 {No results} =1 {One result} other {<x id="INTERPOLATION"/> results}}</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/search/search.component.html</context>
<context context-type="linenumber">16</context>
</context-group>
</trans-unit>
<trans-unit id="41147374f427980a9f1a8cd5e3f4b1666e6f2418" datatype="html">
<source>Paperless-ng</source>
<context-group purpose="location">
@@ -997,123 +1039,109 @@
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="5195932016807797291" datatype="html">
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find(c =&gt; c.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">37</context>
</context-group>
</trans-unit>
<trans-unit id="8170755470576301659" datatype="html">
<source>Without correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="8705701325879965907" datatype="html">
<source>Type: <x id="PH" equiv-text="this.documentTypes.find(dt =&gt; dt.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="4362173610367509215" datatype="html">
<source>Without document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="8180755793012580465" datatype="html">
<source>Tag: <x id="PH" equiv-text="this.tags.find(t =&gt; t.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="6494566478302448576" datatype="html">
<source>Without any tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="6523384805359286307" datatype="html">
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="1872523635812236432" datatype="html">
<source>ASN: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">61</context>
</context-group>
</trans-unit>
<trans-unit id="5701618810648052610" datatype="html">
<source>Title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">88</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="3100631071441658964" datatype="html">
<source>Title &amp; content</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">89</context>
<context context-type="linenumber">78</context>
</context-group>
</trans-unit>
<trans-unit id="7517688192215738656" datatype="html">
<source>ASN</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">90</context>
<context context-type="linenumber">79</context>
</context-group>
</trans-unit>
<trans-unit id="1010505078885609376" datatype="html">
<source>Advanced search</source>
<trans-unit id="5195932016807797291" datatype="html">
<source>Correspondent: <x id="PH" equiv-text="this.correspondents.find(c =&gt; c.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">91</context>
<context context-type="linenumber">33</context>
</context-group>
</trans-unit>
<trans-unit id="2649431021108393503" datatype="html">
<source>More like</source>
<trans-unit id="8170755470576301659" datatype="html">
<source>Without correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">94</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="8705701325879965907" datatype="html">
<source>Type: <x id="PH" equiv-text="this.documentTypes.find(dt =&gt; dt.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">40</context>
</context-group>
</trans-unit>
<trans-unit id="4362173610367509215" datatype="html">
<source>Without document type</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="8180755793012580465" datatype="html">
<source>Tag: <x id="PH" equiv-text="this.tags.find(t =&gt; t.id == +rule.value)?.name"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">46</context>
</context-group>
</trans-unit>
<trans-unit id="6494566478302448576" datatype="html">
<source>Without any tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="6523384805359286307" datatype="html">
<source>Title: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>
<trans-unit id="1872523635812236432" datatype="html">
<source>ASN: <x id="PH" equiv-text="rule.value"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
<context context-type="linenumber">57</context>
</context-group>
</trans-unit>
<trans-unit id="02d184c288f567825a1fcbf83bcd3099a10853d5" datatype="html">
<source>Filter tags</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">19</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="4b089ca12c472cf0b46167bb5afe4b527b301bbc" datatype="html">
<source>Filter correspondents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">27</context>
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
<trans-unit id="0ad509732aaf702b7ea8c771c7809fa84bc85908" datatype="html">
<source>Filter document types</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">34</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="2d9d55f1b70142ff4597ba32179d16888fd9c6b2" datatype="html">
<source>Reset filters</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
<context context-type="linenumber">57</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="7593728289020204896" datatype="html">
@@ -1205,28 +1233,7 @@
<source>Score:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">87</context>
</context-group>
</trans-unit>
<trans-unit id="727d980bba2b3e0b3d8705607f1208eef046479b" datatype="html">
<source>Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="0f5d856cb63c69fde44fbfc653ec0655f9040865" datatype="html">
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="a205126adef6251fc63305f1a6228d07bc2795a8" datatype="html">
<source>Modified: <x id="INTERPOLATION" equiv-text="{{ document.modified | customDate}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
<context context-type="linenumber">45</context>
<context context-type="linenumber">66</context>
</context-group>
</trans-unit>
<trans-unit id="7985804062689412812" datatype="html">
@@ -1407,19 +1414,11 @@
<context context-type="linenumber">68</context>
</context-group>
</trans-unit>
<trans-unit id="0f72a164f5a5d10d1fbdeeb84ac80a63c04b7e1b" datatype="html">
<source>Add item</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
<note priority="1" from="description">Used for both types and correspondents</note>
</trans-unit>
<trans-unit id="a1e6c11f20d4bf6e8e6b43e3c6d2561b2080645e" datatype="html">
<source>Suggestions:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/select/select.component.html</context>
<context context-type="linenumber">31</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="27d158b47717ff9305d19866960418c603f19d55" datatype="html">
@@ -1429,13 +1428,6 @@
<context context-type="linenumber">3</context>
</context-group>
</trans-unit>
<trans-unit id="21fd41eb147f55ca26362f9c398e47733639837a" datatype="html">
<source>Add tag</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/input/tags/tags.component.html</context>
<context context-type="linenumber">11</context>
</context-group>
</trans-unit>
<trans-unit id="4eb84de23219c85432e38fb4fbdeb6c0f103ff8b" datatype="html">
<source>Show all</source>
<context-group purpose="location">
@@ -1634,13 +1626,6 @@
<context context-type="linenumber">14</context>
</context-group>
</trans-unit>
<trans-unit id="bd5ca454e336126f3eeb67417d9264696b5c852c" datatype="html">
<source>Searching document with asn <x id="INTERPOLATION" equiv-text="{{asn}}"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-asn/document-asn.component.html</context>
<context context-type="linenumber">1</context>
</context-group>
</trans-unit>
<trans-unit id="2807800733729323332" datatype="html">
<source>Yes</source>
<context-group purpose="location">
@@ -1739,25 +1724,11 @@
<context context-type="linenumber">100</context>
</context-group>
</trans-unit>
<trans-unit id="792060551707690640" datatype="html">
<source>Polish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">101</context>
</context-group>
</trans-unit>
<trans-unit id="499386805970351976" datatype="html">
<source>Swedish</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">102</context>
</context-group>
</trans-unit>
<trans-unit id="4912706592792948707" datatype="html">
<source>ISO 8601</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/settings.service.ts</context>
<context context-type="linenumber">107</context>
<context context-type="linenumber">106</context>
</context-group>
</trans-unit>
<trans-unit id="2119857572761283468" datatype="html">
@@ -1904,14 +1875,6 @@
<context context-type="linenumber">23</context>
</context-group>
</trans-unit>
<trans-unit id="4460262093225954455" datatype="html">
<source>Search score</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
<context context-type="linenumber">28</context>
</context-group>
<note priority="1" from="description">Score is a value returned by the full text search engine and specifies how well a result matches the given query</note>
</trans-unit>
<trans-unit id="4561076822163447092" datatype="html">
<source>Create new item</source>
<context-group purpose="location">
+4458 -20627
View File
File diff suppressed because it is too large. Load diff
+20 -20
View File
@@ -11,41 +11,41 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~11.2.14",
"@angular/common": "~11.2.14",
"@angular/compiler": "~11.2.14",
"@angular/core": "~11.2.14",
"@angular/forms": "~11.2.14",
"@angular/localize": "~11.2.14",
"@angular/platform-browser": "~11.2.14",
"@angular/platform-browser-dynamic": "~11.2.14",
"@angular/router": "~11.2.14",
"@ng-bootstrap/ng-bootstrap": "^9.1.2",
"@ng-select/ng-select": "^7.0.0",
"@angular/animations": "~10.1.5",
"@angular/common": "~10.1.5",
"@angular/compiler": "~10.1.5",
"@angular/core": "~10.1.5",
"@angular/forms": "~10.1.5",
"@angular/localize": "~10.1.5",
"@angular/platform-browser": "~10.1.5",
"@angular/platform-browser-dynamic": "~10.1.5",
"@angular/router": "~10.1.5",
"@ng-bootstrap/ng-bootstrap": "^8.0.4",
"@ng-select/ng-select": "^5.0.9",
"bootstrap": "^4.5.0",
"file-saver": "^2.0.5",
"ng-bootstrap": "^1.6.3",
"ng2-pdf-viewer": "^6.3.2",
"ngx-bootstrap": "^6.2.0",
"ngx-color": "^6.2.0",
"ngx-cookie-service": "^10.1.1",
"ngx-file-drop": "^11.1.0",
"ngx-file-drop": "^10.0.0",
"ngx-infinite-scroll": "^9.1.0",
"rxjs": "~6.6.0",
"tslib": "^2.0.0",
"uuid": "^8.3.1",
"zone.js": "~0.11.4"
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1102.13",
"@angular/cli": "~11.2.14",
"@angular/compiler-cli": "~11.2.14",
"@types/jasmine": "~3.6.0",
"@angular-devkit/build-angular": "^0.1002.0",
"@angular/cli": "~10.1.5",
"@angular/compiler-cli": "~10.1.5",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.3",
"karma": "~5.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
@@ -53,6 +53,6 @@
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.1.5"
"typescript": "~4.0.2"
}
}
+5 -5
View File
@@ -10,7 +10,7 @@ import { LogsComponent } from './components/manage/logs/logs.component';
import { SettingsComponent } from './components/manage/settings/settings.component';
import { TagListComponent } from './components/manage/tag-list/tag-list.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import {DocumentAsnComponent} from "./components/document-asn/document-asn.component";
import { SearchComponent } from './components/search/search.component';
const routes: Routes = [
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
@@ -18,22 +18,22 @@ const routes: Routes = [
{path: 'dashboard', component: DashboardComponent },
{path: 'documents', component: DocumentListComponent },
{path: 'view/:id', component: DocumentListComponent },
{path: 'search', component: SearchComponent },
{path: 'documents/:id', component: DocumentDetailComponent },
{path: 'asn/:id', component: DocumentAsnComponent },
{path: 'tags', component: TagListComponent },
{path: 'documenttypes', component: DocumentTypeListComponent },
{path: 'correspondents', component: CorrespondentListComponent },
{path: 'logs', component: LogsComponent },
{path: 'settings', component: SettingsComponent },
]},
]},
{path: '404', component: NotFoundComponent},
{path: '**', redirectTo: '/404', pathMatch: 'full'}
];
@NgModule({
imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })],
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
+1 -1
View File
@@ -1,3 +1,3 @@
<app-toasts></app-toasts>
<router-outlet></router-outlet>
<router-outlet></router-outlet>
+2 -2
View File
@@ -18,7 +18,7 @@ export class AppComponent implements OnInit, OnDestroy {
constructor (private settings: SettingsService, private consumerStatusService: ConsumerStatusService, private toastService: ToastService, private router: Router) {
let anyWindow = (window as any)
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js';
anyWindow.pdfWorkerSrc = '/assets/js/pdf.worker.min.js';
this.settings.updateDarkModeSettings()
}
@@ -45,7 +45,7 @@ export class AppComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.consumerStatusService.connect()
this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => {
if (this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)) {
this.toastService.show({title: $localize`Document added`, delay: 10000, content: $localize`Document ${status.filename} was added to paperless.`, actionName: $localize`Open document`, action: () => {
+5 -9
View File
@@ -21,6 +21,8 @@ import { CorrespondentEditDialogComponent } from './components/manage/correspond
import { TagEditDialogComponent } from './components/manage/tag-list/tag-edit-dialog/tag-edit-dialog.component';
import { DocumentTypeEditDialogComponent } from './components/manage/document-type-list/document-type-edit-dialog/document-type-edit-dialog.component';
import { TagComponent } from './components/common/tag/tag.component';
import { SearchComponent } from './components/search/search.component';
import { ResultHighlightComponent } from './components/search/result-highlight/result-highlight.component';
import { PageHeaderComponent } from './components/common/page-header/page-header.component';
import { AppFrameComponent } from './components/app-frame/app-frame.component';
import { ToastsComponent } from './components/common/toasts/toasts.component';
@@ -63,7 +65,6 @@ import { LocalizedDateParserFormatter } from './utils/ngb-date-parser-formatter'
import { ApiVersionInterceptor } from './interceptors/api-version.interceptor';
import { ColorSliderModule } from 'ngx-color/slider';
import { ColorComponent } from './components/common/input/color/color.component';
import { DocumentAsnComponent } from './components/document-asn/document-asn.component';
import localeFr from '@angular/common/locales/fr';
import localeNl from '@angular/common/locales/nl';
@@ -74,9 +75,6 @@ import localeEnGb from '@angular/common/locales/en-GB';
import localeRo from '@angular/common/locales/ro';
import localeRu from '@angular/common/locales/ru';
import localeEs from '@angular/common/locales/es';
import localePl from '@angular/common/locales/pl';
import localeSv from '@angular/common/locales/sv';
import localeLb from '@angular/common/locales/lb';
registerLocaleData(localeFr)
@@ -89,9 +87,6 @@ registerLocaleData(localeEnGb)
registerLocaleData(localeRo)
registerLocaleData(localeRu)
registerLocaleData(localeEs)
registerLocaleData(localePl)
registerLocaleData(localeSv)
registerLocaleData(localeLb)
@NgModule({
declarations: [
@@ -110,6 +105,8 @@ registerLocaleData(localeLb)
TagEditDialogComponent,
DocumentTypeEditDialogComponent,
TagComponent,
SearchComponent,
ResultHighlightComponent,
PageHeaderComponent,
AppFrameComponent,
ToastsComponent,
@@ -141,8 +138,7 @@ registerLocaleData(localeLb)
SafePipe,
CustomDatePipe,
DateComponent,
ColorComponent,
DocumentAsnComponent
ColorComponent
],
imports: [
BrowserModule,
@@ -5,7 +5,7 @@
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand col-auto col-md-3 col-lg-2 mr-0 px-3 py-3 order-sm-0" routerLink="/dashboard">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" class="mr-2" fill="currentColor">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1rem" class="mr-2" fill="currentColor">
<path d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z" transform="translate(0 0)"/>
</svg>
<ng-container i18n="app title">Paperless-ng</ng-container>
@@ -175,7 +175,7 @@
</svg>&nbsp;<ng-container i18n>GitHub</ng-container>
</a>
<a class="nav-link-additional small text-muted ml-3" target="_blank" rel="noopener noreferrer" href="https://github.com/jonaswinkler/paperless-ng/discussions/categories/feature-requests" title="Suggest an idea">
<svg xmlns="http://www.w3.org/2000/svg" width="1.3em" height="1.3em" fill="currentColor" class="bi bi-lightbulb pr-1" viewBox="0 0 16 16">
<svg xmlns="http://www.w3.org/2000/svg" width=".9rem" height=".9rem" fill="currentColor" class="bi bi-lightbulb pr-1" viewBox="0 0 16 16">
<path d="M2 6a6 6 0 1 1 10.174 4.31c-.203.196-.359.4-.453.619l-.762 1.769A.5.5 0 0 1 10.5 13a.5.5 0 0 1 0 1 .5.5 0 0 1 0 1l-.224.447a1 1 0 0 1-.894.553H6.618a1 1 0 0 1-.894-.553L5.5 15a.5.5 0 0 1 0-1 .5.5 0 0 1 0-1 .5.5 0 0 1-.46-.302l-.761-1.77a1.964 1.964 0 0 0-.453-.618A5.984 5.984 0 0 1 2 6zm6-5a5 5 0 0 0-3.479 8.592c.263.254.514.564.676.941L5.83 12h4.342l.632-1.467c.162-.377.413-.687.676-.941A5 5 0 0 0 8 1z"/>
</svg>
<ng-container i18n>Suggest an idea</ng-container>
@@ -10,8 +10,6 @@ import { SearchService } from 'src/app/services/rest/search.service';
import { environment } from 'src/environments/environment';
import { DocumentDetailComponent } from '../document-detail/document-detail.component';
import { Meta } from '@angular/platform-browser';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { FILTER_FULLTEXT_QUERY } from 'src/app/data/filter-rule-type';
@Component({
selector: 'app-app-frame',
@@ -26,7 +24,6 @@ export class AppFrameComponent implements OnInit {
private openDocumentsService: OpenDocumentsService,
private searchService: SearchService,
public savedViewService: SavedViewService,
private list: DocumentListViewService,
private meta: Meta
) {
@@ -77,7 +74,7 @@ export class AppFrameComponent implements OnInit {
search() {
this.closeMenu()
this.list.quickFilter([{rule_type: FILTER_FULLTEXT_QUERY, value: this.searchField.value}])
this.router.navigate(['search'], {queryParams: {query: this.searchField.value}})
}
closeDocument(d: PaperlessDocument) {
@@ -30,7 +30,7 @@ export class ConfirmDialogComponent implements OnInit {
@Input()
buttonsEnabled = true
confirmButtonEnabled = true
seconds = 0
@@ -55,7 +55,7 @@
</button>
</div>
</div>
</div>
</div>
</div>
@@ -83,7 +83,7 @@ export class FilterableDropdownSelectionModel {
if (fireEvent) {
this.changed.next(this)
}
}
private getNonTemporary(id: number) {
@@ -10,7 +10,7 @@
<path d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/>
</svg>
</ng-container>
</div>
<div class="mr-1">
<app-tag *ngIf="isTag; else displayName" [tag]="item" [clickable]="true" linkTitle="Filter by tag"></app-tag>
@@ -11,7 +11,7 @@ export class AbstractInputComponent<T> implements OnInit, ControlValueAccessor {
constructor() { }
onChange = (newValue: T) => {};
onTouched = () => {};
writeValue(newValue: any): void {
@@ -2,4 +2,4 @@
<input type="checkbox" class="custom-control-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
<label class="custom-control-label" [for]="inputId">{{title}}</label>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
</div>
</div>
@@ -15,7 +15,7 @@ import { AbstractInputComponent } from '../abstract-input';
})
export class CheckComponent extends AbstractInputComponent<boolean> {
constructor() {
constructor() {
super()
}
@@ -5,12 +5,12 @@
<div class="input-group-prepend">
<span class="input-group-text" [style.background-color]="value">&nbsp;&nbsp;&nbsp;</span>
</div>
<ng-template #popContent>
<div style="min-width: 200px;" class="pb-3">
<color-slider [color]="value" (onChangeComplete)="colorChanged($event.color.hex)"></color-slider>
</div>
</ng-template>
<input class="form-control" [class.is-invalid]="error" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" [autoClose]="'outside'" [ngbPopover]="popContent" placement="bottom" popoverClass="shadow">
@@ -30,4 +30,4 @@
<div class="invalid-feedback">
{{error}}
</div>
</div>
</div>
@@ -11,4 +11,4 @@
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
</div>
</div>
@@ -1,38 +1,33 @@
<div class="form-group paperless-input-select">
<label [for]="inputId">{{title}}</label>
<div [class.input-group]="allowCreateNew">
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
[style.color]="textColor"
[style.background]="backgroundColor"
[clearable]="allowNull"
[items]="items"
[addTag]="allowCreateNew && addItemRef"
addTagText="Add item"
i18n-addTagText="Used for both types and correspondents"
bindLabel="name"
bindValue="id"
(change)="onChange(value)"
(search)="onSearch($event)"
(focus)="clearLastSearchTerm()"
(clear)="clearLastSearchTerm()"
(blur)="onBlur()">
</ng-select>
<div *ngIf="allowCreateNew" class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="addItem()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>
</button>
</div>
<div [class.input-group]="showPlusButton()">
<ng-select name="inputId" [(ngModel)]="value"
[disabled]="disabled"
[style.color]="textColor"
[style.background]="backgroundColor"
[clearable]="allowNull"
[items]="items"
bindLabel="name"
bindValue="id"
(change)="onChange(value)"
(blur)="onTouched()">
</ng-select>
<div *ngIf="showPlusButton()" class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="createNew.emit()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus" />
</svg>
</button>
</div>
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
<small *ngIf="getSuggestions().length > 0">
<span i18n>Suggestions:</span>&nbsp;
<ng-container *ngFor="let s of getSuggestions()">
<a (click)="value = s.id; onChange(value)" [routerLink]="">{{s.name}}</a>&nbsp;
</ng-container>
</small>
</div>
@@ -16,7 +16,6 @@ export class SelectComponent extends AbstractInputComponent<number> {
constructor() {
super()
this.addItemRef = this.addItem.bind(this)
}
@Input()
@@ -35,13 +34,9 @@ export class SelectComponent extends AbstractInputComponent<number> {
suggestions: number[]
@Output()
createNew = new EventEmitter<string>()
createNew = new EventEmitter()
public addItemRef: (name) => void
private _lastSearchTerm: string
get allowCreateNew(): boolean {
showPlusButton(): boolean {
return this.createNew.observers.length > 0
}
@@ -53,29 +48,4 @@ export class SelectComponent extends AbstractInputComponent<number> {
}
}
addItem(name: string) {
if (name) this.createNew.next(name)
else this.createNew.next(this._lastSearchTerm)
this.clearLastSearchTerm()
}
clickNew() {
this.createNew.next(this._lastSearchTerm)
this.clearLastSearchTerm()
}
clearLastSearchTerm() {
this._lastSearchTerm = null
}
onSearch($event) {
this._lastSearchTerm = $event.term
}
onBlur() {
setTimeout(() => {
this.clearLastSearchTerm()
}, 3000);
}
}
@@ -7,14 +7,8 @@
[closeOnSelect]="false"
[clearSearchOnAdd]="true"
[hideSelected]="true"
[addTag]="createTagRef"
addTagText="Add tag"
i18n-addTagText
(change)="onChange(value)"
(search)="onSearch($event)"
(focus)="clearLastSearchTerm()"
(clear)="clearLastSearchTerm()"
(blur)="onBlur()">
(blur)="onTouched()">
<ng-template ng-label-tmp let-item="item">
<span class="tag-wrap tag-wrap-delete" (click)="removeTag(item.id)">
@@ -45,8 +39,8 @@
<ng-container *ngFor="let tag of getSuggestions()">
<a (click)="addTag(tag.id)" [routerLink]="">{{tag.name}}</a>&nbsp;
</ng-container>
</small>
</div>
@@ -17,9 +17,8 @@ import { TagService } from 'src/app/services/rest/tag.service';
})
export class TagsComponent implements OnInit, ControlValueAccessor {
constructor(private tagService: TagService, private modalService: NgbModal) {
this.createTagRef = this.createTag.bind(this)
}
constructor(private tagService: TagService, private modalService: NgbModal) { }
onChange = (newValue: number[]) => {};
@@ -57,10 +56,6 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
tags: PaperlessTag[]
public createTagRef: (name) => void
private _lastSearchTerm: string
getTag(id) {
if (this.tags) {
return this.tags.find(tag => tag.id == id)
@@ -79,11 +74,9 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
}
}
createTag(name: string = null) {
createTag() {
var modal = this.modalService.open(TagEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (name) modal.componentInstance.object = { name: name }
else if (this._lastSearchTerm) modal.componentInstance.object = { name: this._lastSearchTerm }
modal.componentInstance.success.subscribe(newTag => {
this.tagService.listAll().subscribe(tags => {
this.tags = tags.results
@@ -106,18 +99,4 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
this.onChange(this.value)
}
clearLastSearchTerm() {
this._lastSearchTerm = null
}
onSearch($event) {
this._lastSearchTerm = $event.term
}
onBlur() {
setTimeout(() => {
this.clearLastSearchTerm()
}, 3000);
}
}
@@ -5,4 +5,4 @@
<div class="invalid-feedback">
{{error}}
</div>
</div>
</div>
@@ -12,4 +12,4 @@
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-primary" (click)="selectClicked.emit(selected)" i18n>Select</button>
</div>
</div>
@@ -1,2 +1,2 @@
<span *ngIf="!clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</span>
<a [routerLink]="" [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>
<a [routerLink]="" [title]="linkTitle" *ngIf="clickable" class="badge" [style.background]="tag.color" [style.color]="tag.text_color">{{tag.name}}</a>
@@ -1,5 +1,5 @@
<app-page-header title="Dashboard" [subTitle]="subtitle" i18n-title>
<svg class="logo d-none d-md-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.64" height="6em">
<svg class="logo d-none d-md-block" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.64" height="5rem">
<path class="leaf" d="M730.71,0C616.3,266.24,66.21,299.25,242.24,728.31c2.2,5.5-40.71,63.81-69.31,112.22-6.6-24.21-14.3-50.61-13.2-52.81C302.75,618.29,57.41,522.58,44.21,384-17.4,494-39.4,686.5,146.53,797.62c1.1,0,9.9,41.81,14.3,62.71-4.4,8.8-8.8,17.6-11,24.2-4.4,11,28.6,9.91,28.6,12.11,3.3-1.1,81.41-138.63,83.61-139.73C704.31,655.7,782.42,257.44,730.71,0ZM105.82,544.58C145.43,580.89,211.44,692,155.33,756.91,168.53,727.21,172.93,660.1,105.82,544.58ZM269.74,670C200.43,506.08,404,325.65,505.18,280.54,298.35,465.37,263.14,602.89,269.74,670Z" transform="translate(0)" style="fill:#17541f"/>
<path class="text" style="fill:#000" d="M1169.27,231.44q.7,17.06.71,38.73t-.71,38a112,112,0,0,1-6.76,38A100.16,100.16,0,0,1,1142.25,380a93.87,93.87,0,0,1-33.76,23.46q-20.25,8.54-46.56,8.53h-36.25V597.5q0,9.94,2.85,14.57c1.89,3.08,3.79,4.86,5.68,5.33a10.54,10.54,0,0,0,9.24,0v18.48h-86V617.4q5.68,2.13,8.53,0,3.56-.71,7.1-5.33t3.56-14.57V166q0-10.66-3.56-15.28t-7.1-6q-2.85-1.42-8.53,0V127.66h104.48q26.31,0,46.56,8.53a93.75,93.75,0,0,1,33.76,23.46,101,101,0,0,1,20.26,33.4A111.05,111.05,0,0,1,1169.27,231.44ZM1118.8,215.8q0-18.48-6-32t-14.22-21.32a74.68,74.68,0,0,0-17.06-12.44q-8.88-4.62-15.28-4.62h-40.52V392.79h40.52q6.39,0,15.28-3.56t17.06-12.44a86,86,0,0,0,14.22-21.67q6-12.8,6-32Zm337.63,389.52a27.35,27.35,0,0,1-2.85,11.72,40.58,40.58,0,0,1-8.17,11.38,54.8,54.8,0,0,1-13.5,9.59,40.07,40.07,0,0,1-18.84,4.27,60,60,0,0,1-30.57-7.82q-13.5-7.82-18.48-20.61a56.12,56.12,0,0,1-6.39-16.35q-9.24,19.18-28.08,32a72.42,72.42,0,0,1-41.58,12.8q-28.44,0-49.76-20.61-12.07-10.67-19.9-27.73t-9.24-39.09q-.71-2.13-.71-8.53v-.71a124.43,124.43,0,0,1,4.26-25.23,113.22,113.22,0,0,1,11-26.3,149.46,149.46,0,0,1,17.77-24.88,128.6,128.6,0,0,1,22.39-20.26,17.39,17.39,0,0,0,4.62-3.2,28.53,28.53,0,0,1,5.33-3.91l2.13-1.06,2.14-1.07q12.8-7.81,22.39-14.22t17.41-12.08a97.25,97.25,0,0,0,8.53-6.4c2.84-2.37,5.93-4.73,9.24-7.1a79.43,79.43,0,0,0,12.09-10,36.51,36.51,0,0,0,8.53-13.5q4.26-7.11,4.26-17.06a67.74,67.74,0,0,0-2.13-17.77,49.09,49.09,0,0,0-14.57-27q-11.73-11.37-26-14.93a38.26,38.26,0,0,0-12.08-2.13q-19.9,0-34.12,7.11-9.24,4.26-13.51,9.59t-4.62,10a10.73,10.73,0,0,0,1.78,7.46,12.91,12.91,0,0,0,5.69,4.26,6,6,0,0,0,1.78.71,6.28,6.28,0,0,1,1.77.72,32.21,32.21,0,0,1,15.28,12.43,34,34,0,0,1,6,19.55,32.78,32.78,0,0,1-32.7,32.7,33.53,33.53,0,0,1-24.17-9.6,30.94,30.94,0,0,1-10-23.1,106.83,106.83,0,0,1,3.2-25.23,84.65,84.65,0,0,1,9.6-23.81q13.5-19.91,32.34-28.79a96.24,96.24,0,0,1,41.58-8.89,93.82,93.82,0,0,1,33.76,6.4,96.18,96.18,0,0,1,29.86,18.13,103.38,103.38,0,0,1,22,28.07q8.88,16.35,8.88,37.68v209q0,12.79.36,16.7t.36,5.34h0q0,9.24,5.68,14.92a18.45,18.45,0,0,0,13.51,5.69q9.24,0,14.92-5.33a20.62,20.62,0,0,0,6.4-13.86Zm-111.6-17.77a52.58,52.58,0,0,0,6.76-21.33q1.07-11.37,1.06-21.32V413.4a63.92,63.92,0,0,1-6,5.33,74,74,0,0,0-6.75,6q-20.62,14.93-35.19,27.37a164.06,164.06,0,0,0-24.52,25.59,113,113,0,0,0-15.64,28.07,121,121,0,0,0-7.11,34.12v10.67q.72,27,14.58,42.29t39.44,15.28a38.56,38.56,0,0,0,20.62-5.69q9.24-6.39,12.79-14.92Zm244.52-304.23q40.51.72,64.68,24.53t24.17,75.7V542.77q0,51.88-24.17,75.34T1589.35,643h-2.13a80.27,80.27,0,0,1-22-2.84h-.71a.63.63,0,0,1-.71-.71,91.74,91.74,0,0,1-28.43-15.64l-6.76-6.76-5.33-7.46v118q0,10,2.85,14.58c1.89,3.08,3.79,5.09,5.68,6a10.54,10.54,0,0,0,9.24,0V766h-86V748.19q5.68,2.13,8.53,0,3.56-1.43,6.75-6t3.2-14.58V326.68q0-9.94-3.2-14.57t-6.75-6q-2.85-1.43-8.53,0V289h68.23v27.72a68.68,68.68,0,0,1,12.09-14.21,75.62,75.62,0,0,1,28.43-15.64.63.63,0,0,1,.71-.71h.71a90.55,90.55,0,0,1,22-2.85Zm35.54,84.59v-.71q-.72-36.26-8.89-51.18t-26.65-16.35a63.12,63.12,0,0,0-24.17,3.2,48,48,0,0,0-19.9,13.15q-8.54,9.24-14.22,24.88t-7.82,37V548.45q2.13,22,7.82,37.32t14.22,24.17a53.23,53.23,0,0,0,19.9,13.15,58.92,58.92,0,0,0,24.17,3.55q18.48-1.42,26.65-16.35t8.89-51.18V367.91Zm179.83-84.59a105.36,105.36,0,0,1,33.41,5.69,86.38,86.38,0,0,1,30.56,17.77,88.06,88.06,0,0,1,21.68,30.57q8.18,18.48,8.18,44.06v90.28H1757.1V544.9q0,44.07,15.28,62.9t38,18.13a113.94,113.94,0,0,0,26.65-4.62,54.83,54.83,0,0,0,22.75-13.15q9.59-9.24,16-26.3t6.4-44.78h16.35V547a153.56,153.56,0,0,1-4.27,36.61A72,72,0,0,1,1852,634.1q-16.71,7.47-40.87,8.18a102.25,102.25,0,0,1-34.83-6.4,96,96,0,0,1-32-19.19,108.91,108.91,0,0,1-23.45-30.21q-9.24-17.41-9.25-39.45v-16.7s-.11-9.12-.35-27.37-.36-29.5-.36-33.76-.11-16-.35-35.19-.36-29.49-.36-30.92v-21q0-25.59,8.89-44.43t22-30.56A90.72,90.72,0,0,1,1771,289.37a98.55,98.55,0,0,1,33.76-6.05Zm46.2,169.89V397.05q0-27-3.55-45.13t-9.6-29.15q-6-11-14.57-15.28a42.36,42.36,0,0,0-19.19-4.26q-22.76,0-34.48,19.9t-11.72,73.92v56.16Zm279.35-113.73a42.31,42.31,0,0,0,.71,5.68,17.92,17.92,0,0,1,0,7.11q-.72,14.93-10.31,24.52a33,33,0,0,1-46.91-.35,33.52,33.52,0,0,1-9.6-24.17q0-17.76,13.51-27a8.33,8.33,0,0,0,1.77-1.06l1.07-1.07a16.22,16.22,0,0,0,6.4-10.31q1.41-6.75-8.53-11.73a26.26,26.26,0,0,0-12.8-3.55q-21.32-1.43-37.67,12.8t-22,49.75V597.5q0,9.94,2.84,14.57t5.69,5.33a10.54,10.54,0,0,0,9.24,0v18.48h-86V617.4q5.68,2.13,8.53,0,3.55-.71,7.11-5.33t3.55-14.57V326.68q0-9.94-3.55-14.57t-7.11-6q-2.85-1.43-8.53,0V288.3h68.24v29.14q5-7.81,9.95-14.57a48.44,48.44,0,0,1,11.72-11.37,59.79,59.79,0,0,1,15.64-7.46q8.89-2.85,22.39-2.85,20.61,0,38.39,14.93t26.3,43.36Zm95.95,258q0,9.94,3.2,14.57c2.13,3.08,4.15,4.86,6,5.33q3.55,2.13,9.24,0v18.48h-86V617.4q5.68,2.13,10,0,2.13-.71,5.69-5.33t3.55-14.57V166.75q0-10.65-3.55-15.28a28.82,28.82,0,0,0-5.69-6q-4.26-2.13-10,0V128.37h67.52V597.5Zm154.25-314.18a105.25,105.25,0,0,1,33.4,5.69,86.33,86.33,0,0,1,30.57,17.77,88.19,88.19,0,0,1,21.68,30.57q8.18,18.48,8.17,44.06v90.28H2332.84V544.9q0,44.07,15.29,62.9t38,18.13a113.93,113.93,0,0,0,Line truncated
</svg>
@@ -19,6 +19,6 @@
<app-statistics-widget></app-statistics-widget>
<app-upload-file-widget></app-upload-file-widget>
</div>
</div>
@@ -23,7 +23,7 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy {
statistics: Statistics = {}
subscription: Subscription
private getStatistics(): Observable<Statistics> {
return this.http.get(`${environment.apiBaseUrl}statistics/`)
}
@@ -13,4 +13,4 @@
<p i18n>Consult the documentation on how to use these features. The section on basic usage also has some information on how to use paperless in general.</p>
</ng-container>
</app-widget-frame>
</app-widget-frame>
@@ -4,9 +4,9 @@
<h5 class="card-title mb-0">{{title}}</h5>
<ng-content select ="[header-buttons]"></ng-content>
</div>
</div>
<div class="card-body text-dark">
<ng-content select ="[content]"></ng-content>
</div>
</div>
</div>
@@ -1 +0,0 @@
<p i18n>Searching document with asn {{asn}}</p>
Whitespace-only changes.
@@ -1,34 +0,0 @@
import { Component, OnInit } from '@angular/core';
import {DocumentService} from "../../services/rest/document.service";
import {ActivatedRoute, Router} from "@angular/router";
import {FILTER_ASN} from "../../data/filter-rule-type";
@Component({
selector: 'app-document-asncomponent',
templateUrl: './document-asn.component.html',
styleUrls: ['./document-asn.component.scss']
})
export class DocumentAsnComponent implements OnInit {
asn: string
constructor(
private documentsService: DocumentService,
private route: ActivatedRoute,
private router: Router) { }
ngOnInit(): void {
this.route.paramMap.subscribe(paramMap => {
this.asn = paramMap.get('id');
this.documentsService.listAllFilteredIds([{rule_type: FILTER_ASN, value: this.asn}]).subscribe(documentId => {
if (documentId.length == 1) {
this.router.navigate(['documents', documentId[0]])
} else {
this.router.navigate(['404'])
}
})
})
}
}
@@ -60,9 +60,9 @@
<app-input-number i18n-title title="Archive serial number" [error]="error?.archive_serial_number" formControlName='archive_serial_number'></app-input-number>
<app-input-date i18n-title title="Date created" formControlName="created" [error]="error?.created"></app-input-date>
<app-input-select [items]="correspondents" i18n-title title="Correspondent" formControlName="correspondent" [allowNull]="true"
(createNew)="createCorrespondent($event)" [suggestions]="suggestions?.correspondents"></app-input-select>
(createNew)="createCorrespondent()" [suggestions]="suggestions?.correspondents"></app-input-select>
<app-input-select [items]="documentTypes" i18n-title title="Document type" formControlName="document_type" [allowNull]="true"
(createNew)="createDocumentType($event)" [suggestions]="suggestions?.document_types"></app-input-select>
(createNew)="createDocumentType()" [suggestions]="suggestions?.document_types"></app-input-select>
<app-input-tags formControlName="tags" [suggestions]="suggestions?.tags"></app-input-tags>
</ng-template>
@@ -139,7 +139,7 @@
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
<ng-template #nativePdfViewer>
<object [data]="previewUrl | safe" class="preview-sticky" width="100%"></object>
<object [data]="previewUrl | safe" type="application/pdf" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
@@ -20,7 +20,6 @@ import { ToastService } from 'src/app/services/toast.service';
import { TextComponent } from '../common/input/text/text.component';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { PaperlessDocumentSuggestions } from 'src/app/data/paperless-document-suggestions';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
@Component({
selector: 'app-document-detail',
@@ -128,10 +127,9 @@ export class DocumentDetailComponent implements OnInit {
this.documentForm.patchValue(doc)
}
createDocumentType(newName: string) {
createDocumentType() {
var modal = this.modalService.open(DocumentTypeEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName }
modal.componentInstance.success.subscribe(newDocumentType => {
this.documentTypeService.listAll().subscribe(documentTypes => {
this.documentTypes = documentTypes.results
@@ -140,10 +138,9 @@ export class DocumentDetailComponent implements OnInit {
})
}
createCorrespondent(newName: string) {
createCorrespondent() {
var modal = this.modalService.open(CorrespondentEditDialogComponent, {backdrop: 'static'})
modal.componentInstance.dialogMode = 'create'
if (newName) modal.componentInstance.object = { name: newName }
modal.componentInstance.success.subscribe(newCorrespondent => {
this.correspondentService.listAll().subscribe(correspondents => {
this.correspondents = correspondents.results
@@ -222,7 +219,7 @@ export class DocumentDetailComponent implements OnInit {
}
moreLike() {
this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}])
this.router.navigate(["search"], {queryParams: {more_like:this.document.id}})
}
hasNext() {
@@ -20,4 +20,4 @@
</tr>
</tbody>
</table>
</div>
</div>
@@ -1,3 +1,3 @@
.metadata-column {
overflow-wrap: anywhere;
}
}
@@ -25,14 +25,14 @@
</h5>
</div>
<p class="card-text">
<span *ngIf="document.__search_hit__" [innerHtml]="document.__search_hit__.highlights"></span>
<span *ngIf="!document.__search_hit__" class="result-content">{{contentTrimmed}}</span>
<app-result-highlight *ngIf="getDetailsAsHighlight()" class="result-content" [highlights]="getDetailsAsHighlight()"></app-result-highlight>
<span *ngIf="getDetailsAsString()" class="result-content">{{getDetailsAsString()}}</span>
</p>
<div class="d-flex flex-column flex-md-row align-items-md-center">
<div class="btn-group">
<a class="btn btn-sm btn-outline-secondary" (click)="clickMoreLike.emit()">
<a routerLink="/search" [queryParams]="{'more_like': document.id}" class="btn btn-sm btn-outline-secondary" *ngIf="moreLikeThis">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-three-dots" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
</svg>&nbsp;<span class="d-block d-md-inline" i18n>More like this</span>
@@ -62,6 +62,10 @@
</div>
<div class="list-group list-group-horizontal border-0 card-info ml-md-auto mt-2 mt-md-0">
<div *ngIf="searchScore" class="list-group-item bg-light text-dark p-1 mr-5 border-0 d-flex search-score">
<small class="text-muted" i18n>Score:</small>
<ngb-progressbar [type]="searchScoreClass" [value]="searchScore" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
</div>
<button *ngIf="document.document_type" type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 mr-2" title="Filter by document type"
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<svg class="metadata-icon mr-2 text-muted bi bi-file-earmark" viewBox="0 0 16 16" fill="currentColor">
@@ -82,11 +86,6 @@
</svg>
<small>{{document.created | customDate:'mediumDate'}}</small>
</div>
<div *ngIf="document.__search_hit__?.score" class="list-group-item bg-light text-dark border-0 d-flex p-0 pl-4 search-score">
<small class="text-muted" i18n>Score:</small>
<ngb-progressbar [type]="searchScoreClass" [value]="document.__search_hit__.score" class="search-score-bar mx-2 mt-1" [max]="1"></ngb-progressbar>
</div>
</div>
</div>
@@ -60,8 +60,3 @@
padding-top: 0.35rem !important;
}
}
span ::ng-deep .match {
color: black;
background-color: rgb(255, 211, 66);
}
@@ -4,8 +4,6 @@ import { PaperlessDocument } from 'src/app/data/paperless-document';
import { DocumentService } from 'src/app/services/rest/document.service';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
@Component({
selector: 'app-document-card-large',
@@ -26,9 +24,15 @@ export class DocumentCardLargeComponent implements OnInit {
return this.toggleSelected.observers.length > 0
}
@Input()
moreLikeThis: boolean = false
@Input()
document: PaperlessDocument
@Input()
details: any
@Output()
clickTag = new EventEmitter<number>()
@@ -38,8 +42,8 @@ export class DocumentCardLargeComponent implements OnInit {
@Output()
clickDocumentType = new EventEmitter<number>()
@Output()
clickMoreLike= new EventEmitter()
@Input()
searchScore: number
@ViewChild('popover') popover: NgbPopover
@@ -47,14 +51,12 @@ export class DocumentCardLargeComponent implements OnInit {
popoverHidden = true
get searchScoreClass() {
if (this.document.__search_hit__) {
if (this.document.__search_hit__.score > 0.7) {
return "success"
} else if (this.document.__search_hit__.score > 0.3) {
return "warning"
} else {
return "danger"
}
if (this.searchScore > 0.7) {
return "success"
} else if (this.searchScore > 0.3) {
return "warning"
} else {
return "danger"
}
}
@@ -65,6 +67,19 @@ export class DocumentCardLargeComponent implements OnInit {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}
getDetailsAsString() {
if (typeof this.details === 'string') {
return this.details.substring(0, 500)
}
}
getDetailsAsHighlight() {
//TODO: this is not an exact typecheck, can we do better
if (this.details instanceof Array) {
return this.details
}
}
getThumbUrl() {
return this.documentService.getThumbUrl(this.document.id)
}
@@ -101,8 +116,4 @@ export class DocumentCardLargeComponent implements OnInit {
mouseLeaveCard() {
this.popover.close()
}
get contentTrimmed() {
return this.document.content.substr(0, 500)
}
}
@@ -38,15 +38,7 @@
<small>{{(document.document_type$ | async)?.name}}</small>
</button>
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
<ng-template #dateTooltip>
<div class="d-flex flex-column">
<span i18n>Created: {{ document.created | customDate}}</span>
<span i18n>Added: {{ document.added | customDate}}</span>
<span i18n>Modified: {{ document.modified | customDate}}</span>
</div>
</ng-template>
<div class="pl-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
<div class="pl-0 p-1" placement="top" ngbTooltip="Added:&nbsp;{{document.added | customDate:'mediumDate'}} Created:&nbsp;{{document.created | customDate:'mediumDate'}}">
<svg class="metadata-icon mr-2 text-muted bi bi-calendar-event" viewBox="0 0 16 16" fill="currentColor">
<path d="M11 6.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5v-1z"/>
<path d="M3.5 0a.5.5 0 0 1 .5.5V1h8V.5a.5.5 0 0 1 1 0V1h1a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h1V.5a.5.5 0 0 1 .5-.5zM1 4v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V4H1z"/>
@@ -75,8 +75,8 @@
</app-page-header>
<div class="sticky-top py-2 mt-n2 mt-sm-n3 py-sm-4 bg-body mx-n3 px-3">
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" #filterEditor></app-filter-editor>
<div class="w-100 mb-2 mb-sm-4">
<app-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [rulesModified]="filterRulesModified" (filterRulesChange)="rulesChanged()" (reset)="resetFilters()" #filterEditor></app-filter-editor>
<app-bulk-editor [hidden]="!isBulkEditing"></app-bulk-editor>
</div>
@@ -89,95 +89,86 @@
[rotate]="true" aria-label="Default pagination"></ngb-pagination>
</div>
<ng-container *ngIf="list.error ; else documentListNoError">
<div class="alert alert-danger" role="alert">Error while loading documents: {{list.error}}</div>
</ng-container>
<div *ngIf="displayMode == 'largeCards'">
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" [details]="d.content" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)">
</app-document-card-large>
</div>
<ng-template #documentListNoError>
<table class="table table-sm border shadow-sm" *ngIf="displayMode == 'details'">
<thead>
<th></th>
<th class="d-none d-lg-table-cell"
sortable="archive_serial_number"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>ASN</th>
<th class="d-none d-md-table-cell"
sortable="correspondent__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Correspondent</th>
<th
sortable="title"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Title</th>
<th class="d-none d-xl-table-cell"
sortable="document_type__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Document type</th>
<th
sortable="created"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Created</th>
<th class="d-none d-xl-table-cell"
sortable="added"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Added</th>
</thead>
<tbody>
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" (click)="toggleSelected(d, $event)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
<td>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event)">
<label class="custom-control-label" for="docCheck{{d.id}}"></label>
</div>
</td>
<td class="d-none d-lg-table-cell">
{{d.archive_serial_number}}
</td>
<td class="d-none d-md-table-cell">
<ng-container *ngIf="d.correspondent">
<a [routerLink]="" (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent">{{(d.correspondent$ | async)?.name}}</a>
</ng-container>
</td>
<td>
<a routerLink="/documents/{{d.id}}" title="Edit document" style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
</td>
<td class="d-none d-xl-table-cell">
<ng-container *ngIf="d.document_type">
<a [routerLink]="" (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type">{{(d.document_type$ | async)?.name}}</a>
</ng-container>
</td>
<td>
{{d.created | customDate}}
</td>
<td class="d-none d-xl-table-cell">
{{d.added | customDate}}
</td>
</tr>
</tbody>
</table>
<div *ngIf="displayMode == 'largeCards'">
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickMoreLike)="clickMoreLike(d.id)">
</app-document-card-large>
</div>
<table class="table table-sm border shadow-sm" *ngIf="displayMode == 'details'">
<thead>
<th></th>
<th class="d-none d-lg-table-cell"
sortable="archive_serial_number"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>ASN</th>
<th class="d-none d-md-table-cell"
sortable="correspondent__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Correspondent</th>
<th
sortable="title"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Title</th>
<th class="d-none d-xl-table-cell"
sortable="document_type__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Document type</th>
<th
sortable="created"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Created</th>
<th class="d-none d-xl-table-cell"
sortable="added"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Added</th>
</thead>
<tbody>
<tr *ngFor="let d of list.documents; trackBy: trackByDocumentId" (click)="toggleSelected(d, $event)" [ngClass]="list.isSelected(d) ? 'table-row-selected' : ''">
<td>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="docCheck{{d.id}}" [checked]="list.isSelected(d)" (click)="toggleSelected(d, $event)">
<label class="custom-control-label" for="docCheck{{d.id}}"></label>
</div>
</td>
<td class="d-none d-lg-table-cell">
{{d.archive_serial_number}}
</td>
<td class="d-none d-md-table-cell">
<ng-container *ngIf="d.correspondent">
<a [routerLink]="" (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent">{{(d.correspondent$ | async)?.name}}</a>
</ng-container>
</td>
<td>
<a routerLink="/documents/{{d.id}}" title="Edit document" style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
<app-tag [tag]="t" *ngFor="let t of d.tags$ | async" class="ml-1" clickable="true" linkTitle="Filter by tag" (click)="clickTag(t.id);$event.stopPropagation()"></app-tag>
</td>
<td class="d-none d-xl-table-cell">
<ng-container *ngIf="d.document_type">
<a [routerLink]="" (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type">{{(d.document_type$ | async)?.name}}</a>
</ng-container>
</td>
<td>
{{d.created | customDate}}
</td>
<td class="d-none d-xl-table-cell">
{{d.added | customDate}}
</td>
</tr>
</tbody>
</table>
<div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
</div>
</ng-template>
<div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
</div>
@@ -34,12 +34,3 @@ $paperless-card-breakpoints: (
right: 0 !important;
left: auto !important;
}
.sticky-top {
z-index: 990; // below main navbar
top: calc(7rem - 2px); // height of navbar (mobile)
@media (min-width: 580px) {
top: 3.5rem; // height of navbar
}
}
@@ -2,14 +2,12 @@ import { Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subscription } from 'rxjs';
import { FilterRule, isFullTextFilterRule } from 'src/app/data/filter-rule';
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view';
import { SortableDirective, SortEvent } from 'src/app/directives/sortable.directive';
import { ConsumerStatusService } from 'src/app/services/consumer-status.service';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { DOCUMENT_SORT_FIELDS, DOCUMENT_SORT_FIELDS_FULLTEXT } from 'src/app/services/rest/document.service';
import { DOCUMENT_SORT_FIELDS } from 'src/app/services/rest/document.service';
import { SavedViewService } from 'src/app/services/rest/saved-view.service';
import { ToastService } from 'src/app/services/toast.service';
import { FilterEditorComponent } from './filter-editor/filter-editor.component';
@@ -39,7 +37,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
displayMode = 'smallCards' // largeCards, smallCards, details
unmodifiedFilterRules: FilterRule[] = []
filterRulesModified: boolean = false
private consumptionFinishedSubscription: Subscription
@@ -52,7 +50,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
}
getSortFields() {
return isFullTextFilterRule(this.list.filterRules) ? DOCUMENT_SORT_FIELDS_FULLTEXT : DOCUMENT_SORT_FIELDS
return DOCUMENT_SORT_FIELDS
}
onSort(event: SortEvent) {
@@ -83,12 +81,12 @@ export class DocumentListComponent implements OnInit, OnDestroy {
}
this.list.activateSavedView(view)
this.list.reload()
this.unmodifiedFilterRules = view.filter_rules
this.rulesChanged()
})
} else {
this.list.activateSavedView(null)
this.list.reload()
this.unmodifiedFilterRules = []
this.rulesChanged()
}
})
}
@@ -102,6 +100,7 @@ export class DocumentListComponent implements OnInit, OnDestroy {
loadViewConfig(view: PaperlessSavedView) {
this.list.loadSavedView(view)
this.list.reload()
this.rulesChanged()
}
saveViewConfig() {
@@ -114,7 +113,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
}
this.savedViewService.patch(savedView).subscribe(result => {
this.toastService.showInfo($localize`View "${this.list.activeSavedViewTitle}" saved successfully.`)
this.unmodifiedFilterRules = this.list.filterRules
})
}
}
@@ -143,6 +141,46 @@ export class DocumentListComponent implements OnInit, OnDestroy {
})
}
resetFilters(): void {
this.filterRulesModified = false
if (this.list.activeSavedViewId) {
this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(viewUntouched => {
this.list.filterRules = viewUntouched.filter_rules
this.list.reload()
})
} else {
this.list.filterRules = []
this.list.reload()
}
}
rulesChanged() {
let modified = false
if (this.list.activeSavedViewId == null) {
modified = this.list.filterRules.length > 0 // documents list is modified if it has any filters
} else {
// compare savedView current filters vs original
this.savedViewService.getCached(this.list.activeSavedViewId).subscribe(view => {
let filterRulesInitial = view.filter_rules
if (this.list.filterRules.length !== filterRulesInitial.length) modified = true
else {
modified = this.list.filterRules.some(rule => {
return (filterRulesInitial.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined)
})
if (!modified) {
// only check other direction if we havent already determined is modified
modified = filterRulesInitial.some(rule => {
this.list.filterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined
})
}
}
})
}
this.filterRulesModified = modified
}
toggleSelected(document: PaperlessDocument, event: MouseEvent): void {
if (!event.shiftKey) this.list.toggleSelected(document)
else this.list.selectRangeTo(document)
@@ -169,10 +207,6 @@ export class DocumentListComponent implements OnInit, OnDestroy {
})
}
clickMoreLike(documentID: number) {
this.list.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: documentID.toString()}])
}
trackByDocumentId(index, item: PaperlessDocument) {
return item.id
}
@@ -1,14 +1,15 @@
<div class="row">
<div class="col mb-2 mb-xl-0">
<div class="form-inline d-flex align-items-center">
<label class="text-muted mr-2 mb-0" i18n>Filter by:</label>
<div class="input-group input-group-sm flex-fill w-auto">
<div class="input-group-prepend" ngbDropdown>
<button class="btn btn-outline-primary" ngbDropdownToggle>{{textFilterTargetName}}</button>
<button class="btn btn-outline-primary" ngbDropdownToggle>{{textFilterTargetName}}</button>
<div class="dropdown-menu shadow" ngbDropdownMenu>
<button *ngFor="let t of textFilterTargets" ngbDropdownItem [class.active]="textFilterTarget == t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
</div>
</div>
<input #textFilterInput class="form-control form-control-sm" type="text" [(ngModel)]="textFilter" [readonly]="textFilterTarget == 'fulltext-morelike'">
<input class="form-control form-control-sm" type="text" [(ngModel)]="textFilter">
</div>
</div>
</div>
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '@angular/core';
import { PaperlessTag } from 'src/app/data/paperless-tag';
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
@@ -8,17 +8,13 @@ import { DocumentTypeService } from 'src/app/services/rest/document-type.service
import { TagService } from 'src/app/services/rest/tag.service';
import { CorrespondentService } from 'src/app/services/rest/correspondent.service';
import { FilterRule } from 'src/app/data/filter-rule';
import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_ASN, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY, FILTER_HAS_ANY_TAG, FILTER_HAS_TAG, FILTER_TITLE, FILTER_TITLE_CONTENT } from 'src/app/data/filter-rule-type';
import { FILTER_ADDED_AFTER, FILTER_ADDED_BEFORE, FILTER_ASN, FILTER_CORRESPONDENT, FILTER_CREATED_AFTER, FILTER_CREATED_BEFORE, FILTER_DOCUMENT_TYPE, FILTER_HAS_ANY_TAG, FILTER_HAS_TAG, FILTER_TITLE, FILTER_TITLE_CONTENT } from 'src/app/data/filter-rule-type';
import { FilterableDropdownSelectionModel } from '../../common/filterable-dropdown/filterable-dropdown.component';
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component';
import { DocumentService } from 'src/app/services/rest/document.service';
import { PaperlessDocument } from 'src/app/data/paperless-document';
const TEXT_FILTER_TARGET_TITLE = "title"
const TEXT_FILTER_TARGET_TITLE_CONTENT = "title-content"
const TEXT_FILTER_TARGET_ASN = "asn"
const TEXT_FILTER_TARGET_FULLTEXT_QUERY = "fulltext-query"
const TEXT_FILTER_TARGET_FULLTEXT_MORELIKE = "fulltext-morelike"
@Component({
selector: 'app-filter-editor',
@@ -68,33 +64,20 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
constructor(
private documentTypeService: DocumentTypeService,
private tagService: TagService,
private correspondentService: CorrespondentService,
private documentService: DocumentService
private correspondentService: CorrespondentService
) { }
@ViewChild("textFilterInput")
textFilterInput: ElementRef
tags: PaperlessTag[] = []
correspondents: PaperlessCorrespondent[] = []
documentTypes: PaperlessDocumentType[] = []
_textFilter = ""
_moreLikeId: number
_moreLikeDoc: PaperlessDocument
get textFilterTargets() {
let targets = [
{id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`},
{id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`},
{id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN`},
{id: TEXT_FILTER_TARGET_FULLTEXT_QUERY, name: $localize`Advanced search`}
]
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
targets.push({id: TEXT_FILTER_TARGET_FULLTEXT_MORELIKE, name: $localize`More like`})
}
return targets
}
textFilterTargets = [
{id: TEXT_FILTER_TARGET_TITLE, name: $localize`Title`},
{id: TEXT_FILTER_TARGET_TITLE_CONTENT, name: $localize`Title & content`},
{id: TEXT_FILTER_TARGET_ASN, name: $localize`ASN`}
]
textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
@@ -112,28 +95,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
dateAddedBefore: string
dateAddedAfter: string
_unmodifiedFilterRules: FilterRule[] = []
_filterRules: FilterRule[] = []
@Input()
set unmodifiedFilterRules(value: FilterRule[]) {
this._unmodifiedFilterRules = value
this.checkIfRulesHaveChanged()
}
get unmodifiedFilterRules(): FilterRule[] {
return this._unmodifiedFilterRules
}
@Input()
set filterRules (value: FilterRule[]) {
this._filterRules = value
this.documentTypeSelectionModel.clear(false)
this.tagSelectionModel.clear(false)
this.correspondentSelectionModel.clear(false)
this._textFilter = null
this._moreLikeId = null
this.dateAddedBefore = null
this.dateAddedAfter = null
this.dateCreatedBefore = null
@@ -153,18 +120,6 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
this._textFilter = rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_ASN
break
case FILTER_FULLTEXT_QUERY:
this._textFilter = rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_QUERY
break
case FILTER_FULLTEXT_MORELIKE:
this._moreLikeId = +rule.value
this.textFilterTarget = TEXT_FILTER_TARGET_FULLTEXT_MORELIKE
this.documentService.get(this._moreLikeId).subscribe(result => {
this._moreLikeDoc = result
this._textFilter = result.title
})
break
case FILTER_CREATED_AFTER:
this.dateCreatedAfter = rule.value
break
@@ -191,7 +146,6 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
break
}
})
this.checkIfRulesHaveChanged()
}
get filterRules(): FilterRule[] {
@@ -205,12 +159,6 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_ASN) {
filterRules.push({rule_type: FILTER_ASN, value: this._textFilter})
}
if (this._textFilter && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_QUERY) {
filterRules.push({rule_type: FILTER_FULLTEXT_QUERY, value: this._textFilter})
}
if (this._moreLikeId && this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
filterRules.push({rule_type: FILTER_FULLTEXT_MORELIKE, value: this._moreLikeId?.toString()})
}
if (this.tagSelectionModel.isNoneSelected()) {
filterRules.push({rule_type: FILTER_HAS_ANY_TAG, value: "false"})
} else {
@@ -242,27 +190,12 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
@Output()
filterRulesChange = new EventEmitter<FilterRule[]>()
@Output()
reset = new EventEmitter()
@Input()
rulesModified: boolean = false
private checkIfRulesHaveChanged() {
let modified = false
if (this._unmodifiedFilterRules.length != this._filterRules.length) {
modified = true
} else {
modified = this._unmodifiedFilterRules.some(rule => {
return (this._filterRules.find(fri => fri.rule_type == rule.rule_type && fri.value == rule.value) == undefined)
})
if (!modified) {
// only check other direction if we havent already determined is modified
modified = this._filterRules.some(rule => {
this._unmodifiedFilterRules.find(fr => fr.rule_type == rule.rule_type && fr.value == rule.value) == undefined
})
}
}
this.rulesModified = modified
}
updateRules() {
this.filterRulesChange.next(this.filterRules)
}
@@ -290,12 +223,8 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
distinctUntilChanged()
).subscribe(text => {
this._textFilter = text
this.documentService.searchQuery = text
this.updateRules()
})
if (this._textFilter) this.documentService.searchQuery = this._textFilter
}
ngOnDestroy() {
@@ -303,9 +232,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
}
resetSelected() {
this.textFilterTarget = TEXT_FILTER_TARGET_TITLE_CONTENT
this.filterRules = this._unmodifiedFilterRules
this.updateRules()
this.reset.next()
}
toggleTag(tagId: number) {
@@ -333,11 +260,7 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
}
changeTextFilterTarget(target) {
if (this.textFilterTarget == TEXT_FILTER_TARGET_FULLTEXT_MORELIKE && target != TEXT_FILTER_TARGET_FULLTEXT_MORELIKE) {
this._textFilter = ""
}
this.textFilterTarget = target
this.textFilterInput.nativeElement.focus()
this.updateRules()
}
}
@@ -32,6 +32,6 @@ export class CorrespondentEditDialogComponent extends EditDialogComponent<Paperl
match: new FormControl(""),
is_insensitive: new FormControl(true)
})
}
}
}
@@ -18,7 +18,7 @@ export class CorrespondentListComponent extends GenericListComponent<PaperlessCo
constructor(correspondentsService: CorrespondentService, modalService: NgbModal,
private list: DocumentListViewService,
toastService: ToastService
) {
) {
super(correspondentsService,modalService,CorrespondentEditDialogComponent, toastService)
}
@@ -13,7 +13,7 @@ import { ToastService } from 'src/app/services/toast.service';
})
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
constructor(service: DocumentTypeService, activeModal: NgbActiveModal, toastService: ToastService) {
constructor(service: DocumentTypeService, activeModal: NgbActiveModal, toastService: ToastService) {
super(service, activeModal, toastService)
}
@@ -49,4 +49,4 @@
</td>
</tr>
</tbody>
</table>
</table>
@@ -52,4 +52,4 @@
</td>
</tr>
</tbody>
</table>
</table>
@@ -5,4 +5,4 @@
<path d="M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z"/>
</svg>
<h1 i18n>404 Not Found</h1>
</div>
</div>
@@ -0,0 +1,3 @@
... <span *ngFor="let fragment of highlights">
<span *ngFor="let token of fragment" [class.match]="token.highlight">{{token.text}}</span> ...
</span>
@@ -0,0 +1,4 @@
.match {
color: black;
background-color: rgb(255, 211, 66);
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ResultHighlightComponent } from './result-highlight.component';
describe('ResultHighlightComponent', () => {
let component: ResultHighlightComponent;
let fixture: ComponentFixture<ResultHighlightComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ResultHighlightComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ResultHighlightComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,19 @@
import { Component, Input, OnInit } from '@angular/core';
import { SearchHitHighlight } from 'src/app/data/search-result';
@Component({
selector: 'app-result-highlight',
templateUrl: './result-highlight.component.html',
styleUrls: ['./result-highlight.component.scss']
})
export class ResultHighlightComponent implements OnInit {
constructor() { }
@Input()
highlights: SearchHitHighlight[][]
ngOnInit(): void {
}
}
@@ -0,0 +1,26 @@
<app-page-header i18n-title title="Search results">
</app-page-header>
<div *ngIf="errorMessage" class="alert alert-danger" i18n>Invalid search query: {{errorMessage}}</div>
<p *ngIf="more_like" i18n>Showing documents similar to <a routerLink="/documents/{{more_like}}">{{more_like_doc?.original_file_name}}</a></p>
<p *ngIf="query">
<ng-container i18n>Search query: <i>{{query}}</i></ng-container>
<ng-container *ngIf="correctedQuery">
- <ng-container i18n>Did you mean "<a [routerLink]="" (click)="searchCorrectedQuery()">{{correctedQuery}}</a>"?</ng-container>
</ng-container>
</p>
<div *ngIf="!errorMessage" [class.result-content-searching]="searching" infiniteScroll (scrolled)="onScroll()">
<p i18n>{resultCount, plural, =0 {No results} =1 {One result} other {{{resultCount}} results}}</p>
<ng-container *ngFor="let result of results">
<app-document-card-large *ngIf="result.document"
[document]="result.document"
[details]="result.highlights"
[searchScore]="result.score / maxScore"
[moreLikeThis]="true">
</app-document-card-large>
</ng-container>
</div>
@@ -0,0 +1,15 @@
.result-content {
color: darkgray;
}
.doc-img {
object-fit: cover;
object-position: top;
height: 100%;
position: absolute;
}
.result-content-searching {
opacity: 0.3;
}
@@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentAsnComponent } from './document-asn.component';
import { SearchComponent } from './search.component';
describe('DocumentASNComponentComponent', () => {
let component: DocumentAsnComponent;
let fixture: ComponentFixture<DocumentAsnComponent>;
describe('SearchComponent', () => {
let component: SearchComponent;
let fixture: ComponentFixture<SearchComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DocumentAsnComponent ]
declarations: [ SearchComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DocumentAsnComponent);
fixture = TestBed.createComponent(SearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
@@ -0,0 +1,95 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type';
import { SearchHit } from 'src/app/data/search-result';
import { DocumentService } from 'src/app/services/rest/document.service';
import { SearchService } from 'src/app/services/rest/search.service';
@Component({
selector: 'app-search',
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {
results: SearchHit[] = []
query: string = ""
more_like: number
more_like_doc: PaperlessDocument
searching = false
currentPage = 1
pageCount = 1
resultCount
correctedQuery: string = null
errorMessage: string
get maxScore() {
return this.results?.length > 0 ? this.results[0].score : 100
}
constructor(private searchService: SearchService, private route: ActivatedRoute, private router: Router, private documentService: DocumentService) { }
ngOnInit(): void {
this.route.queryParamMap.subscribe(paramMap => {
window.scrollTo(0, 0)
this.query = paramMap.get('query')
this.more_like = paramMap.has('more_like') ? +paramMap.get('more_like') : null
if (this.more_like) {
this.documentService.get(this.more_like).subscribe(r => {
this.more_like_doc = r
})
} else {
this.more_like_doc = null
}
this.searching = true
this.currentPage = 1
this.loadPage()
})
}
searchCorrectedQuery() {
this.router.navigate(["search"], {queryParams: {query: this.correctedQuery, more_like: this.more_like}})
}
loadPage(append: boolean = false) {
this.errorMessage = null
this.correctedQuery = null
this.searchService.search(this.query, this.currentPage, this.more_like).subscribe(result => {
if (append) {
this.results.push(...result.results)
} else {
this.results = result.results
}
this.pageCount = result.page_count
this.searching = false
this.resultCount = result.count
this.correctedQuery = result.corrected_query
}, error => {
this.searching = false
this.resultCount = 1
this.pageCount = 1
this.results = []
this.errorMessage = error.error
})
}
onScroll() {
if (this.currentPage < this.pageCount) {
this.currentPage += 1
this.loadPage(true)
}
}
}
+1 -8
View File
@@ -22,9 +22,6 @@ export const FILTER_ASN_ISNULL = 18
export const FILTER_TITLE_CONTENT = 19
export const FILTER_FULLTEXT_QUERY = 20
export const FILTER_FULLTEXT_MORELIKE = 21
export const FILTER_RULE_TYPES: FilterRuleType[] = [
{id: FILTER_TITLE, filtervar: "title__icontains", datatype: "string", multi: false, default: ""},
@@ -54,11 +51,7 @@ export const FILTER_RULE_TYPES: FilterRuleType[] = [
{id: FILTER_MODIFIED_AFTER, filtervar: "modified__date__gt", datatype: "date", multi: false},
{id: FILTER_ASN_ISNULL, filtervar: "archive_serial_number__isnull", datatype: "boolean", multi: false},
{id: FILTER_TITLE_CONTENT, filtervar: "title_content", datatype: "string", multi: false},
{id: FILTER_FULLTEXT_QUERY, filtervar: "query", datatype: "string", multi: false},
{id: FILTER_FULLTEXT_MORELIKE, filtervar: "more_like_id", datatype: "number", multi: false},
{id: FILTER_TITLE_CONTENT, filtervar: "title_content", datatype: "string", multi: false}
]
export interface FilterRuleType {
+2 -8
View File
@@ -1,22 +1,16 @@
import { FILTER_FULLTEXT_MORELIKE, FILTER_FULLTEXT_QUERY } from "./filter-rule-type"
export function cloneFilterRules(filterRules: FilterRule[]): FilterRule[] {
if (filterRules) {
let newRules: FilterRule[] = []
for (let rule of filterRules) {
newRules.push({rule_type: rule.rule_type, value: rule.value})
}
return newRules
return newRules
} else {
return null
}
}
export function isFullTextFilterRule(filterRules: FilterRule[]): boolean {
return filterRules.find(r => r.rule_type == FILTER_FULLTEXT_QUERY || r.rule_type == FILTER_FULLTEXT_MORELIKE) != null
}
export interface FilterRule {
rule_type: number
value: string
}
}
@@ -1,7 +1,7 @@
import { MatchingModel } from './matching-model';
export interface PaperlessCorrespondent extends MatchingModel {
last_correspondence?: Date
}
@@ -1,5 +1,5 @@
export interface PaperlessDocumentMetadata {
original_checksum?: string
archived_checksum?: string
@@ -10,4 +10,4 @@ export interface PaperlessDocumentMetadata {
has_archive_version?: boolean
}
}
@@ -6,4 +6,4 @@ export interface PaperlessDocumentSuggestions {
document_types?: number[]
}
}
Loaded 100 of 209 files, more files were not shown because too many files have changed in this diff. Show more