Compare commits

..
Author SHA1 Message Date
jonaswinkler 27ba139810 workaround for issue with pikepdf 2021-05-13 14:15:42 +02:00
jonaswinkler 27ff38b79e Merge branch 'dev' into feature-merge-tool 2021-05-13 14:08:31 +02:00
Jonas Winkler d521d7733b Merge pull request #789 from shamoon/feature/merge-tool-ui
Feature: merge tool UI
2021-04-03 22:26:57 +02:00
Michael Shamoon 99bde7b613 Dont import entire dark mode theme just for 1 color 2021-04-02 14:18:48 -07:00
Michael Shamoon cc5b7eb4e3 Center preview title when empty 2021-04-02 11:10:42 -07:00
Michael Shamoon 2b4a0efef8 Move add to split merge directly into bulk editor 2021-04-02 10:57:06 -07:00
Michael Shamoon 9502917df6 Fix overhang of separator background 2021-04-02 08:05:02 -07:00
Michael Shamoon 6f3c72ef9a Fix options text color not visible in light mode 2021-04-02 07:54:03 -07:00
Michael Shamoon 3915149ea1 Remove separators from first / last position when dragged 2021-04-02 07:51:24 -07:00
Michael Shamoon f07fb6e1b3 Fix tabs in light mode 2021-04-02 07:47:06 -07:00
Michael Shamoon e328c439ff Change to 5/7 column width layout 2021-04-02 07:28:54 -07:00
Michael Shamoon caa9a340a4 Increase height of dropzone 2021-04-02 07:27:42 -07:00
Michael Shamoon b07537aa9a Merge branch 'feature-merge-tool' into feature/merge-tool-ui 2021-04-02 07:11:05 -07:00
jonaswinkler 8e35e6c726 fix test cases 2021-04-02 15:32:00 +02:00
jonaswinkler 05a43a05a4 Merge branch 'dev' into feature-merge-tool 2021-04-02 15:17:44 +02:00
Michael Shamoon 846a997323 Better button responsive layout 2021-03-31 10:35:39 -07:00
Michael Shamoon e0231a4638 Dont allow separator at start of document 2021-03-31 10:24:04 -07:00
Michael Shamoon b4db388d58 Dont initialize page number until page is rendered 2021-03-31 10:23:49 -07:00
Michael Shamoon e957d9aa53 Changes missed from last commit, enables page numbers in previews 2021-03-30 13:56:17 -07:00
Michael Shamoon 72031824bf Preview page numbers 2021-03-25 15:01:05 -07:00
Michael Shamoon ae60fc8c2b Fix popover shadow 2021-03-24 23:23:01 -07:00
Michael Shamoon cc3c7ad481 Apply danger on delete toggle to entire label 2021-03-24 23:21:23 -07:00
Michael Shamoon 484edce5ab Split button should respect pre-selected pages 2021-03-24 23:12:02 -07:00
Michael Shamoon 66aee1446c Light mode fixes, styling tweaks 2021-03-24 22:36:19 -07:00
Michael Shamoon 971392345b Dont allow separator at the end of document parts list 2021-03-24 22:24:18 -07:00
Michael Shamoon c5ae3377bd Basic error handling 2021-03-24 22:16:51 -07:00
Michael Shamoon 4ddd031976 Slightly increasing button spacing on cards 2021-03-24 21:46:18 -07:00
Michael Shamoon c18180f344 Remove debug stuff 2021-03-24 21:46:06 -07:00
Michael Shamoon c07bbcb33f No return type on setter 2021-03-24 21:35:13 -07:00
Michael Shamoon 142c2ac9ff Sanitize pages input 2021-03-24 21:34:43 -07:00
Michael Shamoon 88bf679fd9 Unified button toolbar 2021-03-24 21:08:22 -07:00
Michael Shamoon a5de83fe7f Better clear button 2021-03-24 20:55:19 -07:00
Michael Shamoon 63b4c33fb9 Metadata options & source options merged into options popover panel 2021-03-24 20:37:50 -07:00
Michael Shamoon e0a1ba56dd Typescript fixes 2021-03-24 20:04:16 -07:00
Michael Shamoon aaa913f204 Options popover 2021-03-24 16:16:18 -07:00
Michael Shamoon cf5747f0bd Toggle for source document handling 2021-03-24 13:46:35 -07:00
Michael Shamoon 7df9e1f532 Translation stuff 2021-03-23 14:58:09 -07:00
Michael Shamoon 38d8b34ab4 Clear pages button 2021-03-23 14:57:59 -07:00
Michael Shamoon 24d07fcc17 Missing handle 2021-03-23 14:57:41 -07:00
Michael Shamoon 14116c85e7 Hide preview after all documents removed 2021-03-23 14:28:11 -07:00
Michael Shamoon 20b95fb506 Disable page chooser button if no page chosen 2021-03-23 14:24:42 -07:00
Michael Shamoon 3c25595fc2 Separator styling 2021-03-23 14:21:46 -07:00
Michael Shamoon af1034f84b Visual reorganization 2021-03-23 11:01:10 -07:00
Michael Shamoon cfc0925e04 Split support including tabbed previews 2021-03-23 00:03:21 -07:00
Michael Shamoon 746c03c198 Remove 'mode' concept, basic split UI, many visual changes 2021-03-22 22:31:52 -07:00
Michael Shamoon baae3c2701 Allow removing last document 2021-03-20 22:56:36 -07:00
Michael Shamoon d88cd05857 Allow empty pages field 2021-03-20 22:50:23 -07:00
Michael Shamoon 73c80c3f7a Page field formatting 2021-03-20 22:47:20 -07:00
Michael Shamoon 35f9989255 Allow input-debounce input 2021-03-20 22:04:49 -07:00
Michael Shamoon 33ddd033f2 Allow additional classes for input-debounce & fix field display 2021-03-20 21:45:31 -07:00
Michael Shamoon b8fabb623e InputDebounce component for page fields 2021-03-20 21:32:52 -07:00
Michael Shamoon 37c0fa4fab Merge branch 'feature-merge-tool' into feature/merge-tool-ui 2021-03-18 14:25:24 -07:00
Michael Shamoon 86ca37909b Basic page support 2021-03-16 22:36:59 -07:00
Michael Shamoon ce19525758 Change icons 2021-03-16 20:31:31 -07:00
Michael Shamoon bb872ab56f Tooltip, random styling stuff 2021-03-16 15:31:54 -07:00
Michael Shamoon ea342831da Basic mode toggling 2021-03-16 14:53:23 -07:00
Michael Shamoon 8d0ac690fb Basic page chooser component 2021-03-16 10:29:22 -07:00
jonaswinkler e0f14941e8 added some test cases and validation 2021-03-16 10:20:58 +01:00
Michael Shamoon 7cbb50565c Basic duplicate & delete 2021-03-14 22:12:27 -07:00
Michael Shamoon 274df28f39 Spacer styling 2021-03-14 21:29:47 -07:00
Michael Shamoon 4a6160869c Naming 2021-03-14 21:27:58 -07:00
Michael Shamoon 87d969f44c Basic action buttons 2021-03-14 21:21:13 -07:00
Michael Shamoon 7746ab4522 Basic document chooser 2021-03-14 20:34:32 -07:00
Michael Shamoon 58cb42b44a Typescript 2021-03-14 14:53:32 -07:00
Michael Shamoon 8e330fc97a Basic live preview 2021-03-14 14:40:32 -07:00
Michael Shamoon 6a3ad77950 Skeleton UI for buttons 2021-03-14 14:02:01 -07:00
jonaswinkler 3d56621253 Merge branch 'dev' into feature-merge-tool 2021-03-14 19:22:08 +01:00
Michael Shamoon 9e6cb1b868 Basic layout elements, loading spinner 2021-03-14 09:30:29 -07:00
Michael Shamoon 321fad0564 Basic drag-n-drop 2021-03-13 22:34:47 -08:00
Michael Shamoon eb7b767e35 Basic button for initiating split/merge from bulk editor 2021-03-13 21:00:53 -08:00
jonaswinkler 98b26b8e16 changed page specification 2021-03-13 19:29:11 +01:00
jonaswinkler 176d1fa2eb a couple changes 2021-03-13 18:21:36 +01:00
jonaswinkler 072134366c codestyle 2021-03-13 18:02:52 +01:00
jonaswinkler afd92af168 codestyle 2021-03-13 17:59:56 +01:00
jonaswinkler 2d61698509 couple changes 2021-03-13 17:56:38 +01:00
jonaswinkler 5e286798d5 better error messages 2021-03-13 17:48:51 +01:00
jonaswinkler b7a825301a support for overriding the date created 2021-03-13 17:48:38 +01:00
jonaswinkler 02da012603 added tests for merge 2021-03-13 17:47:54 +01:00
jonaswinkler 4d99e72a0e Merge branch 'dev' into feature-merge-tool 2021-03-12 23:24:16 +01:00
jonaswinkler 26c0568b1e some changes 2021-03-11 22:26:29 +01:00
jonaswinkler aa4d9782b7 initial gui #335 2021-03-11 22:23:01 +01:00
jonaswinkler 43ccf6682c move a couple things around 2021-03-11 22:09:23 +01:00
jonaswinkler 5e096bc8ce Angular interfaces for split+merge API support #335 2021-03-11 18:00:19 +01:00
jonaswinkler 1e131f0217 added a very crude and largely untested API endpoint for document merging #335 2021-03-11 17:58:16 +01:00
jonaswinkler 321869edb0 Merge branch 'dev' into feature-merge-tool 2021-03-11 10:10:31 +01:00
jonaswinkler c6e8faa718 initial implementation of merge tool 2021-03-10 22:26:31 +01:00
jonaswinkler 6696c4d7a5 initial work on merge tool frontend 2021-03-10 21:43:14 +01:00
jonaswinkler 88254bdb79 Merge branch 'dev' into feature-merge-tool 2021-03-10 18:44:55 +01:00
jonaswinkler 4cc6bf889e Added a dummy endpoint with no functionality to the API 2021-02-13 14:56:58 +01:00
195 changed files with 8717 additions and 27539 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
+8 -3
View File
@@ -8,7 +8,7 @@ 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 \
@@ -33,11 +33,16 @@ RUN apt-get update \
zlib1g \
ghostscript \
icc-profiles-free \
# Mime type detection
&& 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 \
# fixes sudo / gosu issues
libseccomp2 \
# Mime type detection
file \
libmagic-dev \
media-types \
# OCRmyPDF dependencies
# OCRmyPDF dependencies
liblept5 \
qpdf \
tesseract-ocr \
+9 -7
View File
@@ -14,14 +14,15 @@ django = "~=3.2"
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
+606 -703
View File
File diff suppressed because it is too large. Load diff
+1 -6
View File
@@ -6,10 +6,6 @@
[![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.
@@ -66,7 +62,7 @@ If you want to see paperless-ng in action, [more screenshots are available in th
# 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.
@@ -104,7 +100,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
+38 -38
View File
@@ -4,37 +4,37 @@ 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
fi
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
}
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."
set +e
chown -R paperless:paperless ../
chown -R paperless:paperless /tmp/paperless
find .. -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} +
set -e
set -e
gosu paperless /sbin/docker-prepare.sh
gosu paperless /sbin/docker-prepare.sh
}
install_languages() {
@@ -50,35 +50,35 @@ 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
+11 -6
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::
@@ -240,8 +247,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]
+11 -53
View File
@@ -5,56 +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
##################
@@ -109,6 +59,17 @@ paperless-ng 1.4.0
To do this, execute the ``document_index reindex`` management command
(see :ref:`administration-index`).
.. 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.3.2
##################
@@ -1405,8 +1366,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
@@ -1482,7 +1441,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
+15 -34
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>
@@ -174,11 +178,11 @@ 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
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::
@@ -188,7 +192,7 @@ PAPERLESS_ADMIN_USER=<username>
the lifecycle of the containers.
PAPERLESS_ADMIN_MAIL=<email>
(Optional) Specify superuser email address. Only used when
(Optional) Specify superuser email address. Only used when
`PAPERLESS_ADMIN_USER` is set.
Defaults to ``root@localhost``.
@@ -222,24 +226,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 +262,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 +626,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.
+25 -35
View File
@@ -13,48 +13,42 @@ 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 | SMTP | |
+=========+================+=====+=====+=====+======+================+
| 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 | `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`_ |
+---------+----------------+-----+-----+-----+------+----------------+
.. _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
@@ -65,10 +59,6 @@ Physical scanners
.. _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
=====================
+6 -26
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
+3 -3
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,13 +200,13 @@ 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
+5 -5
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::
@@ -289,7 +289,7 @@ Matching specific tags, correspondents or types:
Matching dates:
.. code::
created:[2005 to 2009]
added:yesterday
modified:today
@@ -306,11 +306,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 +385,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
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
+45 -48
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.4; python_version >= '3.6'
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'
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.2
django-filter==2.4.0
django-picklefield==3.0.1; python_version >= '3'
django-q==1.3.9
django==3.2.6
django-q==1.3.4
django==3.2
djangorestframework==3.12.4
filelock==3.0.12
fuzzywuzzy[speedup]==0.18.0
gunicorn==20.1.0
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'
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
langdetect==1.0.8
lxml==4.6.3; 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
numpy==1.19.5
ocrmypdf==11.7.3
pathvalidate==2.4.1
pdfminer.six==20201018
pikepdf==2.16.1
pillow==8.3.1
pikepdf==2.11.1
pillow==8.2.0
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
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.17.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.4.4
reportlab==3.5.67; 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.60.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'
+1 -1
View File
@@ -1,5 +1,5 @@
[Unit]
Description=Paperless scheduler
Description=Paperless consumer
Requires=redis.service
[Service]
+2 -2
View File
@@ -27,8 +27,7 @@
"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"
"sv-SE": "src/locale/messages.sv_SE.xlf"
}
},
"architect": {
@@ -73,6 +72,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 = {
}
}));
}
};
};
+1 -24
View File
@@ -1407,19 +1407,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">29</context>
</context-group>
</trans-unit>
<trans-unit id="27d158b47717ff9305d19866960418c603f19d55" datatype="html">
@@ -1429,13 +1421,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">
@@ -1904,14 +1889,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">
+4473 -20627
View File
File diff suppressed because it is too large. Load diff
+21 -20
View File
@@ -11,41 +11,42 @@
},
"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-drag-drop": "^2.0.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 +54,6 @@
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.1.5"
"typescript": "~4.0.2"
}
}
+3 -1
View File
@@ -11,12 +11,14 @@ import { SettingsComponent } from './components/manage/settings/settings.compone
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 { SplitMergeComponent } from './components/split-merge/split-merge.component';
const routes: Routes = [
{path: '', redirectTo: 'dashboard', pathMatch: 'full'},
{path: '', component: AppFrameComponent, children: [
{path: 'dashboard', component: DashboardComponent },
{path: 'documents', component: DocumentListComponent },
{path: 'split_merge', component: SplitMergeComponent },
{path: 'view/:id', component: DocumentListComponent },
{path: 'documents/:id', component: DocumentDetailComponent },
{path: 'asn/:id', component: DocumentAsnComponent },
@@ -33,7 +35,7 @@ const routes: Routes = [
];
@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: () => {
+12 -4
View File
@@ -33,6 +33,7 @@ import { DocumentCardSmallComponent } from './components/document-list/document-
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component';
import { NgxFileDropModule } from 'ngx-file-drop';
import { TextComponent } from './components/common/input/text/text.component';
import { InputDebounceComponent } from './components/common/input/debounce/input-debounce.component';
import { SelectComponent } from './components/common/input/select/select.component';
import { CheckComponent } from './components/common/input/check/check.component';
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
@@ -64,6 +65,10 @@ 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 { SplitMergeComponent } from './components/split-merge/split-merge.component';
import { DndModule } from 'ngx-drag-drop';
import { DocumentChooserComponent } from './components/common/document-chooser/document-chooser.component';
import { PageChooserComponent } from './components/common/page-chooser/page-chooser.component';
import localeFr from '@angular/common/locales/fr';
import localeNl from '@angular/common/locales/nl';
@@ -76,7 +81,6 @@ 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)
@@ -91,7 +95,6 @@ registerLocaleData(localeRu)
registerLocaleData(localeEs)
registerLocaleData(localePl)
registerLocaleData(localeSv)
registerLocaleData(localeLb)
@NgModule({
declarations: [
@@ -121,6 +124,7 @@ registerLocaleData(localeLb)
DocumentCardSmallComponent,
BulkEditorComponent,
TextComponent,
InputDebounceComponent,
SelectComponent,
CheckComponent,
SaveViewConfigDialogComponent,
@@ -142,7 +146,10 @@ registerLocaleData(localeLb)
CustomDatePipe,
DateComponent,
ColorComponent,
DocumentAsnComponent
DocumentAsnComponent,
SplitMergeComponent,
DocumentChooserComponent,
PageChooserComponent
],
imports: [
BrowserModule,
@@ -155,7 +162,8 @@ registerLocaleData(localeLb)
InfiniteScrollModule,
PdfViewerModule,
NgSelectModule,
ColorSliderModule
ColorSliderModule,
DndModule
],
providers: [
DatePipe,
@@ -68,6 +68,13 @@
</svg>&nbsp;<ng-container i18n>Documents</ng-container>
</a>
</li>
<li class="nav-item" *ngIf="splitMergeToolVisible">
<a class="nav-link" routerLink="split_merge" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors"/>
</svg>&nbsp;<ng-container i18n>Split & merge</ng-container>
</a>
</li>
</ul>
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.sidebarViews.length > 0'>
@@ -12,6 +12,7 @@ import { DocumentDetailComponent } from '../document-detail/document-detail.comp
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';
import { SplitMergeService } from 'src/app/services/split-merge.service';
@Component({
selector: 'app-app-frame',
@@ -27,7 +28,8 @@ export class AppFrameComponent implements OnInit {
private searchService: SearchService,
public savedViewService: SavedViewService,
private list: DocumentListViewService,
private meta: Meta
private meta: Meta,
private splitMergeService: SplitMergeService
) {
}
@@ -106,6 +108,10 @@ export class AppFrameComponent implements OnInit {
}
}
get splitMergeToolVisible() {
return this.splitMergeService.hasDocuments()
}
ngOnInit() {
}
@@ -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>
@@ -0,0 +1,15 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title" i18n>Choose Documents</h4>
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="m-n2 row row-cols-paperless-cards">
<app-document-card-small [simpleCard]="true" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents"></app-document-card-small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-outline-primary" [disabled]="list.selected.size == 0" (click)="confirmClicked.emit()" i18n>Add Selected</button>
</div>
@@ -0,0 +1,28 @@
@import "/src/theme";
.modal-body {
max-height: 70vh;
overflow-y: scroll;
}
$paperless-card-breakpoints: (
0: 2, // xs
768px: 3, //md
992px: 4, //lg
1200px: 5, //xl
1400px: 6, // xxl
1600px: 7,
1800px: 8,
2000px: 9
);
.row-cols-paperless-cards {
@each $width, $n_cols in $paperless-card-breakpoints {
@media(min-width: $width) {
> * {
flex: 0 0 auto;
width: 100% / $n_cols;
}
}
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DocumentChooserComponent } from './document-chooser.component';
describe('DocumentChooserComponent', () => {
let component: DocumentChooserComponent;
let fixture: ComponentFixture<DocumentChooserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DocumentChooserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DocumentChooserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,36 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
@Component({
selector: 'app-document-chooser',
templateUrl: './document-chooser.component.html',
styleUrls: ['./document-chooser.component.scss']
})
export class DocumentChooserComponent implements OnInit {
constructor(
public activeModal: NgbActiveModal,
public list: DocumentListViewService
) { }
@Output()
public confirmClicked = new EventEmitter()
ngOnInit(): void {
this.list.selectNone()
this.list.activateSavedView(null)
this.list.reload()
}
cancelClicked() {
this.list.selectNone()
this.activeModal.close()
}
toggleSelected(d: PaperlessDocument, event: MouseEvent): void {
if (!event.shiftKey) this.list.toggleSelected(d)
else this.list.selectRangeTo(d)
}
}
@@ -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>
@@ -0,0 +1,2 @@
<input type="text" [class]="cssClasses" [placeholder]="placeholder" [(ngModel)]="inputValue" (keyup)="keyUp($event)" />
<span *ngIf="allowClear && inputValue?.length > 0" class="clear-wrapper" title="Clear all" (click)="clear()"><span aria-hidden="true" class="clear">×</span></span>
@@ -0,0 +1,23 @@
.clear-wrapper {
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
cursor: pointer;
position: absolute;
top: 5px;
right: 5px;
user-select: none;
width: 17px;
color: #999;
.clear {
display: inline-block;
font-size: 18px;
line-height: 1;
pointer-events: none;
}
&:hover .clear {
color: #D0021B;
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputDebounceComponent } from './text.component';
describe('InputDebounceComponent', () => {
let component: InputDebounceComponent;
let fixture: ComponentFixture<InputDebounceComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InputDebounceComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InputDebounceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,66 @@
import { Component, Input, Output, ElementRef, EventEmitter } from '@angular/core';
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
@Component({
selector: 'app-input-debounce',
templateUrl: './input-debounce.component.html',
styleUrls: ['./input-debounce.component.scss']
})
export class InputDebounceComponent {
@Input()
placeholder: string
@Input()
delay: number = 500
@Input()
classes: string
@Input()
allowClear: boolean = true
@Input()
set pattern(regexStr: string) {
this._pattern = new RegExp(regexStr)
}
_pattern: RegExp
@Output()
value: EventEmitter<string> = new EventEmitter<string>()
@Input()
inputValue: string;
constructor(private elementRef: ElementRef) {
fromEvent(elementRef.nativeElement, 'keyup').pipe(
map(() => this.inputValue),
debounceTime(this.delay),
distinctUntilChanged()
).subscribe(input =>
this.value.emit(input)
);
}
keyUp(event: any) {
if (!this._pattern) return
if (event.key.length == 1 && !this._pattern.test(event.key)) { // e.g. dont do for backspace
// invalid character, prevent input
event.preventDefault()
event.stopImmediatePropagation()
this.inputValue = this.inputValue.slice(this.inputValue.length, 1)
}
}
get cssClasses(): string {
return 'form-control ' + this.classes
}
clear() {
this.inputValue = ''
this.value.emit(this.inputValue)
}
}
@@ -11,4 +11,4 @@
</div>
<small *ngIf="hint" class="form-text text-muted">{{hint}}</small>
</div>
</div>
@@ -8,8 +8,6 @@
[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)"
@@ -9,7 +9,6 @@
[hideSelected]="true"
[addTag]="createTagRef"
addTagText="Add tag"
i18n-addTagText
(change)="onChange(value)"
(search)="onSearch($event)"
(focus)="clearLastSearchTerm()"
@@ -5,4 +5,4 @@
<div class="invalid-feedback">
{{error}}
</div>
</div>
</div>
@@ -0,0 +1,13 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}: {{document.title}}</h4>
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pdf-viewer class="page-chooser-viewer" [class.splitting]="splitting" [src]="previewUrl" [original-size]="false" [zoom]=".25" [autoresize]="true" [show-borders]="true" [show-all]="true" [render-text-mode]="2" (page-rendered)="afterPageRendered($event)" (after-load-complete)="pdfLoaded($event)"></pdf-viewer>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-outline-primary" (click)="confirmClicked()" [disabled]="pages.length == 0">{{title}}</button>
</div>
@@ -0,0 +1,89 @@
@import "src/theme";
.modal-body {
max-height: 70vh;
overflow-y: scroll;
}
::ng-deep .page-chooser-viewer {
min-height: 400px;
width: 100%;
.pdfViewer {
display: flex !important;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
.page:not(.disabled) {
margin: 2px 3% 2rem !important;
cursor: pointer;
.textLayer {
pointer-events: none;
}
&:hover {
.textLayer {
background-color: $primary !important;
opacity: 0.5;
}
}
}
}
&:not(.splitting) {
.pdfViewer {
.page.selected {
&::after {
position: absolute;
top: 0.5rem;
left: 0.5rem;
display: block;
width: 1rem;
height: 1rem;
border-radius: 0.25rem;
content: "";
background: $primary no-repeat 50%/50% 50%;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e");
}
.textLayer {
background-color: $primary !important;
opacity: 0.5;
}
}
}
}
&.splitting {
.pdfViewer {
.page:not(:last-child).selected::after,
.page:not(:last-child):not(.disabled):hover::after {
background-color: $primary;
width: 2rem;
height: 100%;
content: "Split Here";
color: #abb2bf; // dark mode text color
position: absolute;
top: 0;
right: -2rem;
writing-mode: vertical-lr;
padding: 0.5rem;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.1rem;
font-size: 80%;
font-weight: bold;
}
.page.disabled {
.canvasWrapper,
.textLayer {
opacity: 0.25;
pointer-events: none;
}
}
}
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PageChooserComponent } from './page-chooser.component';
describe('PageChooserComponent', () => {
let component: PageChooserComponent;
let fixture: ComponentFixture<PageChooserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PageChooserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageChooserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,90 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { PaperlessDocument, PaperlessDocumentPart } from 'src/app/data/paperless-document';
import { DocumentService } from 'src/app/services/rest/document.service';
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
@Component({
selector: 'app-page-chooser',
templateUrl: './page-chooser.component.html',
styleUrls: ['./page-chooser.component.scss']
})
export class PageChooserComponent implements OnInit {
constructor(
public activeModal: NgbActiveModal,
private documentService: DocumentService
) { }
get previewUrl(): string {
return this.documentService.getPreviewUrl(this.document.id)
}
@Input()
document: PaperlessDocument
@Input()
splitting: boolean = false
public pages: number[]
public enabledPages: number[]
private nPages: number
@Output()
public confirmPages = new EventEmitter<number[]>()
get title(): string {
return this.splitting ? 'Select Split' : 'Select Pages'
}
ngOnInit(): void {
this.pages = (this.document as PaperlessDocumentPart).pages
if (this.splitting && this.pages?.length) this.enabledPages = this.pages.concat([])
if (!this.pages) this.pages = []
}
cancelClicked() {
this.activeModal.close()
}
confirmClicked() {
if (this.splitting) {
// we need pages as the full range since we wont know length of document later
const startPage = this.pages.pop()
for (let page = startPage + 1; page <= this.nPages; page++) {
if (!this.enabledPages || (this.enabledPages && this.enabledPages.indexOf(page) !== -1)) this.pages.push(page)
}
}
this.confirmPages.emit(this.pages.sort((a, b) => { return a - b }))
}
pdfLoaded(pdf: PDFDocumentProxy) {
this.nPages = pdf.numPages
}
public afterPageRendered(pageRenderedEvent: any) {
const pageView = pageRenderedEvent.source /* as PDFPageView */
const div = pageView.div as HTMLDivElement
if (this.splitting && this.enabledPages?.length) {
div.classList.toggle('disabled', this.enabledPages.indexOf(parseInt(div.dataset.pageNumber)) == -1)
} else {
div.classList.toggle('selected', this.pages.indexOf(parseInt(div.dataset.pageNumber)) !== -1)
}
div.onclick = () => {
let pageNum: number = parseInt(div.dataset.pageNumber)
if (this.splitting) { // only select 1 page
if (pageNum == this.nPages || (this.enabledPages?.length && pageNum == Math.max(...this.enabledPages))) return
this.pages = [pageNum]
div.parentNode.childNodes.forEach(pageEl => {
(pageEl as Element).classList.toggle('selected', false)
})
div.classList.toggle('selected', true)
} else {
this.pages.indexOf(pageNum) !== -1 ? this.pages.splice(this.pages.indexOf(pageNum), 1) : this.pages.push(pageNum)
div.classList.toggle('selected', this.pages.indexOf(pageNum) !== -1)
}
}
}
}
@@ -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>
@@ -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/`)
}
@@ -4,11 +4,6 @@ form {
position: relative;
}
.alert-heading {
font-size: 80%;
font-weight: bold;
}
.alerts-hidden {
.btn {
line-height: 1;
@@ -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>
@@ -38,6 +38,12 @@
</svg>&nbsp;<span class="d-none d-lg-inline" i18n>More like this</span>
</button>
<button type="button" class="btn btn-sm btn-outline-primary mr-2" (click)="splitMerge()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors" />
</svg>&nbsp;<span class="d-none d-lg-inline" i18n>Add to split/merge</span>
</button>
<button type="button" class="btn btn-sm btn-outline-primary" (click)="close()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x" />
@@ -21,6 +21,7 @@ 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';
import { SplitMergeService } from 'src/app/services/split-merge.service';
@Component({
selector: 'app-document-detail',
@@ -76,7 +77,8 @@ export class DocumentDetailComponent implements OnInit {
private documentListViewService: DocumentListViewService,
private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService,
private settings: SettingsService) { }
private settings: SettingsService,
private splitMergeService: SplitMergeService) { }
get useNativePdfViewer(): boolean {
return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER)
@@ -225,6 +227,11 @@ export class DocumentDetailComponent implements OnInit {
this.documentListViewService.quickFilter([{rule_type: FILTER_FULLTEXT_MORELIKE, value: this.documentId.toString()}])
}
splitMerge() {
this.splitMergeService.addDocument(this.document)
this.router.navigate(["split_merge"])
}
hasNext() {
return this.documentListViewService.hasNext(this.documentId)
}
@@ -20,4 +20,4 @@
</tr>
</tbody>
</table>
</div>
</div>
@@ -1,3 +1,3 @@
.metadata-column {
overflow-wrap: anywhere;
}
}
@@ -56,6 +56,12 @@
</div>
</div>
<div class="col-auto ml-auto mb-2 mb-xl-0 d-flex">
<button type="button" class="btn btn-sm btn-outline-primary mr-2" (click)="addToSplitMerge()">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors" />
</svg>&nbsp;<span class="d-none d-lg-inline" i18n>Add to split/merge</span>
</button>
<div class="btn-group btn-group-sm mr-2">
<button type="button" class="btn btn-outline-primary btn-sm" (click)="downloadSelected()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, EventEmitter, Output } 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';
@@ -16,6 +16,8 @@ import { MatchingModel } from 'src/app/data/matching-model';
import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service';
import { ToastService } from 'src/app/services/toast.service';
import { saveAs } from 'file-saver';
import { SplitMergeService } from 'src/app/services/split-merge.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-bulk-editor',
@@ -41,7 +43,9 @@ export class BulkEditorComponent {
private modalService: NgbModal,
private openDocumentService: OpenDocumentsService,
private settings: SettingsService,
private toastService: ToastService
private toastService: ToastService,
private splitMergeService: SplitMergeService,
private router: Router
) { }
applyOnClose: boolean = this.settings.get(SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE)
@@ -214,4 +218,9 @@ export class BulkEditorComponent {
saveAs(result, 'documents.zip');
})
}
addToSplitMerge() {
this.splitMergeService.addDocuments(this.list.selectedDocuments)
this.router.navigate(["split_merge"])
}
}
@@ -83,7 +83,7 @@
<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">
<div *ngIf="document.__search_hit__" 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>
@@ -64,4 +64,4 @@
span ::ng-deep .match {
color: black;
background-color: rgb(255, 211, 66);
}
}
@@ -10,7 +10,7 @@
</div>
</div>
<div style="top: 0; right: 0; font-size: large" class="text-right position-absolute mr-1">
<div *ngIf="!simpleCard" style="top: 0; right: 0; font-size: large" class="text-right position-absolute mr-1">
<div *ngFor="let t of getTagsLimited$() | async">
<app-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Filter by tag" i18n-linkTitle></app-tag>
</div>
@@ -22,13 +22,13 @@
<div class="card-body p-2">
<p class="card-text">
<ng-container *ngIf="document.correspondent">
<ng-container *ngIf="document.correspondent && !simpleCard">
<a [routerLink]="" title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="font-weight-bold">{{(document.correspondent$ | async)?.name}}</a>:
</ng-container>
{{document.title | documentTitle}}
</p>
</div>
<div class="card-footer pt-0 pb-2 px-2">
<div *ngIf="!simpleCard" class="card-footer pt-0 pb-2 px-2">
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
<button *ngIf="document.document_type" type="button" class="list-group-item list-group-item-action bg-transparent pl-0 p-1 border-0" title="Filter by document type"
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
@@ -17,12 +17,15 @@ export class DocumentCardSmallComponent implements OnInit {
@Input()
selected = false
@Output()
toggleSelected = new EventEmitter()
@Input()
document: PaperlessDocument
@Input()
simpleCard: boolean = false
@Output()
toggleSelected = new EventEmitter()
@Output()
clickTag = new EventEmitter<number>()
@@ -93,6 +96,6 @@ export class DocumentCardSmallComponent implements OnInit {
}
mouseLeaveCard() {
this.popover.close()
this.popover?.close()
}
}
@@ -2,14 +2,14 @@ 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 { FilterRule } 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';
@@ -52,7 +52,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) {
@@ -3,7 +3,7 @@
<div class="form-inline d-flex align-items-center">
<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>
@@ -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,166 @@
<app-page-header i18n-title title="Split & Merge Editor">
<div class="btn-group mt-2 mt-sm-0">
<button class="btn btn-sm btn-outline-primary" i18n-title title="Add Documents" (click)="chooseDocuments()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#plus-circle" />
</svg>&nbsp;<ng-container i18n>Add Documents</ng-container>
</button>
</div>
<div class="btn-group ml-2 mt-2 mt-sm-0">
<button type="button" class="btn btn-sm btn-outline-primary"
[popoverTitle]="optionsTitle" [autoClose]="'outside'" [ngbPopover]="optionsPopoverBody" popoverClass="shadow" #optionsPopover="ngbPopover">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear" />
</svg>&nbsp;<ng-container i18n>Options</ng-container>
</button>
<ng-template #optionsTitle>
<ng-container i18n>Options</ng-container>
<button type="button" class="close" aria-label="Close" (click)="optionsPopover.close()">
<span aria-hidden="true">&times;</span>
</button>
</ng-template>
<ng-template #optionsPopoverBody>
<div class="form-group row text-dark">
<label for="source_options" class="col-sm-3 col-form-label pt-0" i18n>Delete source documents</label>
<div class="col-sm-9">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="source_options-delete" [(ngModel)]="delete_source_documents">
<label class="custom-control-label" for="source_options-delete" [class.text-danger]="delete_source_documents"><ng-container i18n>Delete</ng-container><br/><small [class.text-muted]="!delete_source_documents" i18n>Source documents will be immediately deleted.</small></label>
</div>
</div>
</div>
<div class="form-group mt-4 row text-dark">
<label for="metadata_options" class="col-sm-3 col-form-label pt-0" i18n>Output document metadata</label>
<div class="col-sm-9">
<div class="custom-control custom-radio">
<input type="radio" id="metadata_setting-copy" name="metadata_setting" class="custom-control-input" [value]="splitMergeMetadata.COPY_FIRST" [(ngModel)]="metadata_setting">
<label class="custom-control-label" for="metadata_setting-copy"><ng-container i18n>First document</ng-container><br/><small class="text-muted" i18n>Metadata will be copied from first document.</small></label>
</div>
<div class="custom-control custom-radio">
<input type="radio" id="metadata_setting-redo" name="metadata_setting" class="custom-control-input" [value]="splitMergeMetadata.REDO" [(ngModel)]="metadata_setting">
<label class="custom-control-label" for="metadata_setting-redo"><ng-container i18n>Redo</ng-container><br/><small class="text-muted" i18n>Metadata detection will be re-run on new document.</small></label>
</div>
</div>
</div>
</ng-template>
</div>
<div class="btn-group ml-2 mr-2 mr-sm-5 mt-2 mt-sm-0">
<button class="btn btn-sm btn-outline-primary" i18n-title title="Save" (click)="save()" [disabled]="splitMergeService.getDocuments().length < 1">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check-circle" />
</svg>&nbsp;<ng-container i18n>Save</ng-container>
</button>
</div>
<div class="btn-group ml-sm-5 mt-2 mt-sm-0">
<button class="btn btn-sm btn-outline-danger" i18n-title title="Cancel" (click)="cancel()">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#slash-circle" />
</svg>&nbsp;<ng-container i18n>Cancel</ng-container>
</button>
</div>
</app-page-header>
<div class="row">
<div class="col-md-6 col-lg-5">
<div class="h-100" dndDropzone (dndDrop)="onDrop($event, documents)">
<div class="spacer bg-primary" dndPlaceholderRef>
</div>
<div *ngFor="let d of documents; let i = index" class="card w-100 mb-3" [class.separator-card]="d.is_separator" [dndDraggable]="d" dndEffectAllowed="move" (dndMoved)="onDragged(d, documents)">
<ng-container *ngIf="d.is_separator">
<div class="separator"></div>
<h6 class="text-muted small font-weight-bold d-inline-block py-2 px-3 mb-0 mx-auto bg-body" i18n>Document End</h6>
<button class="btn btn-sm btn-danger" title="Remove" (click)="removeDocument(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>
</button>
</ng-container>
<div *ngIf="!d.is_separator" class="row no-gutters">
<div class="col-md-3 bg-light d-flex handle" dndHandle>
<svg width="2em" height="100%" viewBox="0 0 16 16" fill="currentColor" class="mx-auto">
<use xlink:href="assets/bootstrap-icons.svg#grip-horizontal" />
</svg>
<div class="doc-thumb" [style.background-image]="'url(' + getThumbUrl(d.id) + ')'"></div>
</div>
<div class="col-md-9">
<div class="card-body p-3">
<h6 class="card-title mb-2">{{d.title}}</h6>
<div class="card-actions">
<div class="btn-toolbar btn-toolbar-sm justify-content-between w-100">
<button class="btn btn-sm btn-outline-danger flex-fill" i18n-title title="Remove" (click)="removeDocument(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg><span class="d-none d-xl-inline">&nbsp;<ng-container i18n>Remove</ng-container></span>
</button>
<button class="btn btn-sm btn-outline-primary ml-3 flex-fill" i18n-title title="Split" (click)="chooseSplit(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#scissors" />
</svg><span class="d-none d-xl-inline">&nbsp;<ng-container i18n>Split</ng-container></span>
</button>
</div>
<div class="input-group input-group-sm w-100 mt-3">
<div class="input-group-prepend">
<div class="input-group-text"><ng-container i18n>Pages</ng-container>:</div>
</div>
<app-input-debounce delay="500" classes="form-control-sm" placeholder="All" ngbTooltip="e.g. 1-3,5" pattern="[0-9\,\-]" [inputValue]="formatPages(d.pages)" (value)="pagesFieldChange($event, d, i)"></app-input-debounce>
<div class="input-group-append">
<button class="btn btn-outline-primary" type="button" i18n-title title="Select Pages" (click)="choosePages(d, i)">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#pencil" />
</svg><span class="d-none d-xl-inline">&nbsp;<ng-container i18n>Select Pages</ng-container></span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-7">
<ngb-alert *ngIf="error" type="danger" class="mt-2 mb-0" [dismissible]="false">
<h6 class="alert-heading" i18n>An error occurred during operation</h6>
<p class="mb-0 pb-1" i18n>Please check document settings and parts.</p>
<p class="mb-0 pb-1 small"><ng-container i18n>Message</ng-container>: <span class="font-italic">{{error}}</span></p>
</ngb-alert>
<ng-container *ngIf="!error && previewUrls.length > 1">
<ul ngbNav #previewNav="ngbNav" class="nav-tabs">
<li *ngFor="let previewUrl of previewUrls; let j = index" [ngbNavItem]="j">
<a class="bg-light" ngbNavLink><ng-container i18n>Document</ng-container> {{j + 1}}</a>
<ng-template ngbNavContent>
<div class="preview-container px-2 tabbed text-center bg-light">
<ng-container [ngTemplateOutlet]="previewHeader" [ngTemplateOutletContext]="{ $implicit: j }"></ng-container>
<pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2" [(page)]="previewCurrentPages[j]" (after-load-complete)="pdfPreviewLoaded($event, j)" (page-rendered)="pdfPageRendered($event, j)"></pdf-viewer>
</div>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="previewNav"></div>
</ng-container>
<div *ngIf="!error && previewUrls.length <= 1" class="preview-container px-2 text-center bg-light">
<ng-container [ngTemplateOutlet]="previewHeader" [ngTemplateOutletContext]="{ $implicit: 0 }"></ng-container>
<pdf-viewer *ngIf="previewUrls.length > 0" [src]="previewUrls[0]" [original-size]="false" [show-borders]="true" [show-all]="true" [render-text-mode]="2" [(page)]="previewCurrentPages[0]" (after-load-complete)="pdfPreviewLoaded($event, 0)" (page-rendered)="pdfPageRendered($event, 0)"></pdf-viewer>
</div>
</div>
</div>
<ng-template #previewHeader let-index>
<div class="d-flex mt-2 p-2 w-100 justify-space-between">
<h6 class="text-muted mt-1" [class.mx-auto]="previewNumPages[index] == undefined" [class.mr-auto]="previewNumPages[index] !== undefined" i18n>Preview</h6>
<div *ngIf="previewNumPages[index] !== undefined" class="input-group input-group-sm w-auto">
<div class="input-group-prepend">
<div class="input-group-text" i18n>Page</div>
</div>
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages[index]" [(ngModel)]="previewCurrentPages[index]" />
<div class="input-group-append">
<div class="input-group-text" i18n>of {{previewNumPages[index]}}</div>
</div>
</div>
</div>
<div *ngIf="loading" class="spinner-border" role="status">
<span class="sr-only" i18n>Loading...</span>
</div>
</ng-template>
@@ -0,0 +1,116 @@
@import "/src/theme";
.card {
.handle {
cursor: move;
}
.doc-thumb {
background-position: top center;
background-size: cover;
background-repeat: no-repeat;
min-height: 5rem;
height: 100%;
width: 70%;
}
&.separator-card {
border: none;
background: transparent;
}
}
.spacer {
min-height: 0.25rem;
width: 100%;
margin: 1rem;
}
.preview-container {
height: calc(100vh - 170px);
overflow: hidden;
&.tabbed {
height: calc(100vh - 200px);
}
.spinner-border {
margin-top: 40%;
}
}
.split-document-preview ::ng-deep .ng2-pdf-viewer-container {
width: 1px !important;
height: 1px !important;
overflow: hidden !important;
}
::ng-deep .document-selector {
width: 80vw;
min-width: 50vw;
}
button span {
margin-left: 0.15rem;
}
app-input-debounce {
flex: 1 1;
position: relative;
::ng-deep .form-control {
border-radius: 0;
}
}
.nav-link.bg-light {
border-bottom-color: #f8f9fa;
&:not(.active) {
background-color: #fff !important;
border-bottom-color: #dee2e6;
}
}
.separator-card {
.separator {
border: 0;
height: 20px;
opacity: 0.5;
background-color: transparent !important;
background-image: url("data:image/svg+xml;utf8, <svg viewBox='0 0 200 110' xmlns='http://www.w3.org/2000/svg'><path d='M -15 110 L100 10 L215 110' fill='none' stroke='%23000000' stroke-width='1' vector-effect='non-scaling-stroke'/></svg>");
background-position: bottom left;
background-size: 2% auto;
background-repeat: repeat-x;
}
h6 {
text-transform: uppercase;
text-align: center;
letter-spacing: 0.1em;
margin-top: -20px !important;
z-index: 10;
}
button {
transition: all 0.3s ease;
opacity: 0;
pointer-events: none;
position: absolute;
top: 0;
right: 0;
}
&:hover button {
opacity: 1;
pointer-events: all;
}
}
::ng-deep .popover {
min-width: 30rem;
.close {
line-height: 0.7;
}
}
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SplitMergeComponent } from './split-merge.component';
describe('SplitMergeComponent', () => {
let component: SplitMergeComponent;
let fixture: ComponentFixture<SplitMergeComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SplitMergeComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SplitMergeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,212 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { SplitMergeMetadata } from 'src/app/data/split-merge-request';
import { SplitMergeService } from 'src/app/services/split-merge.service';
import { DocumentService } from 'src/app/services/rest/document.service';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';
import { PaperlessDocument, PaperlessDocumentPart } from 'src/app/data/paperless-document';
import { DndDropEvent } from 'ngx-drag-drop';
import { Subject, Subscription } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { DocumentChooserComponent } from 'src/app/components/common/document-chooser/document-chooser.component'
import { PageChooserComponent } from 'src/app/components/common/page-chooser/page-chooser.component'
import { PDFDocumentProxy } from 'ng2-pdf-viewer';
@Component({
selector: 'app-split-merge',
templateUrl: './split-merge.component.html',
styleUrls: ['./split-merge.component.scss']
})
export class SplitMergeComponent implements OnInit, OnDestroy {
public loading: boolean = false
public previewUrls: string[] = []
public previewNumPages: number[] = []
public previewCurrentPages: number[] = []
public delete_source_documents: boolean = false
public metadata_setting: SplitMergeMetadata = SplitMergeMetadata.COPY_FIRST
private previewDebounce$ = new Subject()
private previewSub: Subscription
public error: string
constructor(
public splitMergeService: SplitMergeService,
private documentService: DocumentService,
private list: DocumentListViewService,
private router: Router,
private modalService: NgbModal
) { }
ngOnInit() {
this.previewSub = this.previewDebounce$.pipe(
debounceTime(400)
).subscribe(() => {
if (this.splitMergeService.hasDocuments()) {
this.save(true)
} else {
this.previewUrls = []
this.error = undefined
}
})
this.previewDebounce$.next()
}
ngOnDestroy() {
this.previewDebounce$.complete()
}
get documents(): PaperlessDocument[] {
return this.splitMergeService.getDocuments()
}
getThumbUrl(documentId: number) {
return this.documentService.getThumbUrl(documentId)
}
get splitMergeMetadata() {
return SplitMergeMetadata
}
chooseDocuments() {
let modal = this.modalService.open(DocumentChooserComponent, { backdrop: 'static', size: 'xl' })
modal.componentInstance.confirmClicked.subscribe(() => {
modal.componentInstance.buttonsEnabled = false
this.splitMergeService.addDocuments(this.list.selectedDocuments)
this.list.selectNone()
modal.close()
this.previewDebounce$.next()
})
}
onDragged(document: PaperlessDocument, documents: PaperlessDocument[]) {
const index = this.documents.indexOf(document)
this.documents.splice(index, 1)
}
onDrop(event: DndDropEvent, documents: PaperlessDocument[]) {
let index = event.index
if (typeof index === "undefined") {
index = this.documents.length
}
this.documents.splice(index, 0, event.data)
this.previewDebounce$.next()
}
cancel() {
this.splitMergeService.clear()
this.router.navigate([""])
}
save(preview: boolean = false) {
this.loading = true
this.previewUrls = []
this.splitMergeService.executeSplitMerge(preview, this.delete_source_documents, this.metadata_setting).subscribe(
result => {
this.loading = false
this.error = undefined
if (preview) {
this.previewUrls = result.map(r => this.splitMergeService.getPreviewUrl(r))
} else {
this.splitMergeService.clear()
this.router.navigate([""])
}
},
error => {
this.error = error
this.loading = false
}
)
}
removeDocument(d: PaperlessDocument, index: number) {
this.splitMergeService.removeDocument(d, index)
this.previewNumPages[index] = this.previewCurrentPages[index] = undefined
this.previewDebounce$.next()
}
choosePages(d: PaperlessDocument, index: number) {
let modal = this.modalService.open(PageChooserComponent, { backdrop: 'static', size: 'lg' })
modal.componentInstance.document = d
modal.componentInstance.confirmPages.subscribe((pages) => {
this.splitMergeService.setDocumentPages(d, index, pages)
modal.componentInstance.buttonsEnabled = false
modal.close()
this.previewDebounce$.next()
})
}
chooseSplit(d: PaperlessDocument, index: number) {
let modal = this.modalService.open(PageChooserComponent, { backdrop: 'static', size: 'lg' })
modal.componentInstance.document = d
const enabledPages = (d as PaperlessDocumentPart).pages
modal.componentInstance.splitting = true
modal.componentInstance.confirmPages.subscribe((pages) => {
this.splitMergeService.splitDocument(d, index, pages, enabledPages)
modal.componentInstance.buttonsEnabled = false
modal.close()
this.previewDebounce$.next()
})
}
pagesFieldChange(pageStr: string, d: PaperlessDocument, index: number) {
let pages = pageStr.split(',').map(p => {
if (p.indexOf('-') !== -1) {
const minmax = p.split('-')
let range = []
for (let i = parseInt(minmax[0]); i <= parseInt(minmax[1]); i++) {
range.push(i)
}
return range
} else if (p.length > 0) {
return parseInt(p)
} else {
return null
}
})
pages = [].concat.apply([], pages) // e.g. flat()
pages = pages.filter(page => page !== null)
this.splitMergeService.setDocumentPages(d, index, pages as number[])
this.previewDebounce$.next()
}
formatPages(pages: number[]): string {
let pageStrings = []
let rangeStart, rangeEnd
pages?.forEach(page => {
if (rangeStart == undefined) {
rangeStart = page
} else if (page - rangeStart == 1) {
rangeEnd = page
} else if (rangeEnd !== undefined && page - rangeEnd == 1) {
rangeEnd = page
} else {
pageStrings.push(rangeEnd !== undefined ? rangeStart + '-' + rangeEnd : rangeStart)
rangeStart = page
rangeEnd = undefined
}
})
if (rangeEnd !== undefined) {
pageStrings.push(rangeStart + '-' + rangeEnd)
} else if (rangeStart !== undefined) {
pageStrings.push(rangeStart)
}
return pageStrings.join(',')
}
pdfPreviewLoaded(pdf: PDFDocumentProxy, index: number) {
this.previewNumPages[index] = pdf.numPages
}
pdfPageRendered(event: any, index) { // CustomEvent is es6
if (event.pageNumber > 0 && this.previewCurrentPages[index] == undefined) this.previewCurrentPages[index] = 1
}
}
+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[]
}
}
@@ -52,3 +52,11 @@ export interface PaperlessDocument extends ObjectWithId {
__search_hit__?: SearchHit
}
export interface PaperlessDocumentPart extends PaperlessDocument {
pages: number[]
}
export interface PaperlessDocumentSeparator extends PaperlessDocument {
is_separator: boolean
}
+1 -1
View File
@@ -15,4 +15,4 @@ export interface PaperlessSavedView extends ObjectWithId {
filter_rules: FilterRule[]
}
}
+1 -1
View File
@@ -3,5 +3,5 @@ export interface Results<T> {
count: number
results: T[]
}
@@ -0,0 +1,29 @@
export enum SplitMergeMetadata {
REDO = "redo",
COPY_FIRST = "copy_first"
}
export interface SplitMergePlanDocumentPart {
document: number,
pages?: string
}
export type SplitMergePlan = SplitMergePlanDocumentPart[][]
export interface SplitMergeRequest {
split_merge_plan: SplitMergePlan
delete_source: boolean
metadata: SplitMergeMetadata
preview: boolean
}
@@ -8,4 +8,4 @@ export interface WebsocketConsumerStatusMessage {
message?: string
document_id: number
}
}
@@ -19,7 +19,7 @@ export class ApiVersionInterceptor implements HttpInterceptor {
'Accept': `application/json; version=${environment.apiVersion}`
}
})
return next.handle(request);
}
}
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more