Compare commits

...
Author SHA1 Message Date
Jonas Winkler e46f93444b Merge branch 'dev' into consumer_status 2020-11-23 00:01:32 +01:00
Jonas Winkler c99668b355 changelog 2020-11-22 23:42:57 +01:00
Jonas Winkler fa5df5d28e modular dashboard 2020-11-22 22:35:39 +01:00
Jonas Winkler 4b47f4929e fix release script 2020-11-22 22:35:25 +01:00
Jonas Winkler e44b918af2 more layout 2020-11-22 22:07:55 +01:00
Jonas Winkler a4b268cdae shadows are awesome 2020-11-22 21:16:14 +01:00
Jonas Winkler d9ae4322ea display tags in thumbnail 2020-11-22 21:06:48 +01:00
Jonas Winkler a316521ea6 more layout 2020-11-22 20:32:41 +01:00
Jonas Winkler a214536a51 logo 2020-11-22 20:11:44 +01:00
Jonas Winkler acca5be585 more layout 2020-11-22 20:07:41 +01:00
Jonas Winkler b622b3fc6a more layout 2020-11-22 20:00:58 +01:00
Jonas Winkler 63953b85e9 layout changes 2020-11-22 19:30:55 +01:00
Jonas Winkler 6c0e0755b9 menu closing on mobile 2020-11-22 17:48:54 +01:00
Jonas Winkler 8e7a3d309f page title 2020-11-22 16:39:25 +01:00
Jonas Winkler 09a9ab7a34 layout changes 2020-11-22 16:38:47 +01:00
Jonas Winkler c11ce4e06f favicon 2020-11-22 16:38:27 +01:00
Jonas Winkler 8650af05f7 many layout and theme changes 2020-11-22 16:33:26 +01:00
Jonas Winkler 25f88b7ae9 now using SCSS for better theming support 2020-11-22 14:43:59 +01:00
Jonas Winkler e75f48d148 changelog 2020-11-22 13:55:31 +01:00
Jonas Winkler d8e27600be workaround for a bug in django-q: task results with too long names would not show up in the result lists. 2020-11-22 13:53:19 +01:00
Jonas Winkler 9e31429732 travis fixes 2020-11-22 13:37:24 +01:00
Jonas Winkler 1e0020b56b travis fixes 2020-11-22 13:31:54 +01:00
Jonas Winkler 79e6b1f5dd travis fixes 2020-11-22 13:25:59 +01:00
Jonas Winkler 5a292426c9 codestyle 2020-11-22 13:23:46 +01:00
Jonas Winkler d2df1b0fc9 updated travis 2020-11-22 13:20:20 +01:00
Jonas Winkler fec9e54049 new setting: PAPERLESS_OCR_PAGES 2020-11-22 12:54:08 +01:00
Jonas Winkler f1c9c32142 more status 2020-11-22 12:25:20 +01:00
Jonas Winkler b75d36cc84 Merge branch 'dev' into consumer_status 2020-11-22 11:55:00 +01:00
Jonas Winkler ea089de3b3 added a test case for the index 2020-11-22 11:42:30 +01:00
Jonas Winkler 172b37239f changed a few things with the mail rule admin. 2020-11-22 11:42:17 +01:00
Jonas Winkler 388e5b56de reversible migrations. 2020-11-22 11:41:13 +01:00
Jonas Winkler 532d5c1744 a couple styling changes, collapsible menu 2020-11-22 11:35:04 +01:00
Jonas Winkler 54af13e4b8 much better mail rule admin 2020-11-22 01:39:48 +01:00
Jonas Winkler d65a118d8a use docker compose for building 2020-11-22 00:35:19 +01:00
Jonas Winkler af3d161f66 updated the admin, ordering for mail rules 2020-11-21 23:12:34 +01:00
Jonas Winkler d3482a4aef changelog 2020-11-21 20:44:35 +01:00
Jonas Winkler 3600e5a8fb updated docs 2020-11-21 20:29:30 +01:00
Jonas Winkler 3afee66aaa updated entrypoint script to wait for postgres 2020-11-21 20:14:48 +01:00
Jonas Winkler 110c5c392c added tests to pycodestyle ignore for now. 79 characters really doesnt work there and i don't really care enough. 2020-11-21 16:07:28 +01:00
Jonas Winkler db4519a644 url patterns cleanup 2020-11-21 15:34:30 +01:00
Jonas Winkler 450fb877f6 code cleanup 2020-11-21 15:34:00 +01:00
Jonas Winkler b44f8383e4 code cleanup 2020-11-21 14:03:45 +01:00
Jonas Winkler 5a84cc835a updated release script 2020-11-21 13:05:17 +01:00
Jonas Winkler 529cc04fd1 code cleanup 2020-11-21 12:12:19 +01:00
Jonas Winkler b7fec4d355 using mime type checking during upload 2020-11-21 01:42:55 +01:00
Jonas Winkler 77559332bc docs 2020-11-20 18:45:44 +01:00
Jonas Winkler 321adb5df2 making the migration reversible 2020-11-20 18:45:37 +01:00
Jonas Winkler 09acb134b7 updated mail: now uses mime type detection 2020-11-20 18:14:42 +01:00
Jonas Winkler 3d5b66c2b7 FileType does not care about the extension anymore. 2020-11-20 16:18:59 +01:00
Jonas Winkler 41650f20f4 mime type handling 2020-11-20 13:31:03 +01:00
Jonas Winkler bd45a804a7 docs 2020-11-20 13:28:30 +01:00
Jonas Winkler 3d6b555deb Merge branch 'master' into dev 2020-11-20 11:32:34 +01:00
Jonas Winkler 8681cad77c add required packages to travis 2020-11-20 11:29:34 +01:00
Jonas Winkler 28ea67f252 removed some empty folders. 2020-11-20 11:28:30 +01:00
Jonas Winkler 1255ecf86e update dependencies. 2020-11-20 11:28:19 +01:00
Jonas Winkler 2c9555015b make the index dir if it does not exist. 2020-11-20 11:21:09 +01:00
Jonas Winkler 1655d85a53 testing the tesseract parser 2020-11-19 20:31:08 +01:00
Jonas Winkler c7726fda01 this is really neat, but is it safe? 2020-11-05 10:53:37 +01:00
151 changed files with 2051 additions and 986 deletions

No files matched your search

+31 -5
View File
@@ -1,13 +1,39 @@
language: python
python:
- "3.6"
- "3.7"
- "3.8"
jobs:
include:
- name: "Paperless on Python 3.6"
python: "3.6"
- name: "Paperless on Python 3.7"
python: "3.7"
- name: "Paperless on Python 3.8"
python: "3.8"
- name: "Documentation"
script:
- cd docs/
- make html
after_success: true
- name: "Front end"
language: node_js
node_js:
- 15
before_install: true
install:
- cd src-ui/
- npm install -g @angular/cli
- npm install
script:
- ng build --prod
after_success: true
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq libpoppler-cpp-dev unpaper tesseract-ocr
- sudo apt-get install -qq libpoppler-cpp-dev unpaper tesseract-ocr imagemagick ghostscript
install:
- pip install --upgrade pipenv
+24 -24
View File
@@ -9,43 +9,43 @@ verify_ssl = true
name = "piwheels"
[packages]
django = "~=3.1"
pillow = "*"
dateparser = "~=0.7"
dateparser = "~=0.7.6"
django = "~=3.1.3"
django-cors-headers = "*"
djangorestframework = "~=3.12"
python-gnupg = "*"
python-dotenv = "*"
filemagic = "*"
pyocr = "~=0.7"
django-extensions = "*"
django-filter = "~=2.4.0"
django-q = "~=1.3.4"
djangorestframework = "~=3.12.2"
fuzzywuzzy = "*"
gunicorn = "*"
imap-tools = "*"
langdetect = "*"
pdftotext = "*"
django-filter = "~=2.4"
python-dateutil = "*"
psycopg2-binary = "*"
scikit-learn="~=0.23"
whoosh="~=2.7"
gunicorn = "*"
whitenoise = "~=5.2"
fuzzywuzzy = "*"
python-Levenshtein = "*"
django-extensions = "*"
watchdog = "*"
pathvalidate = "*"
django-q = "*"
pillow = "*"
pyocr = "~=0.7.2"
python-gnupg = "*"
python-dotenv = "*"
python-dateutil = "*"
python-Levenshtein = "*"
python-magic = "*"
psycopg2-binary = "*"
redis = "*"
imap-tools = "*"
scikit-learn="~=0.23.2"
whitenoise = "~=5.2.0"
watchdog = "*"
whoosh="~=2.7.4"
[dev-packages]
coveralls = "*"
factory-boy = "*"
sphinx = "~=3.3"
tox = "*"
pycodestyle = "*"
pytest = "*"
pytest-cov = "*"
pytest-django = "*"
pytest-sugar = "*"
pytest-env = "*"
pytest-sugar = "*"
pytest-xdist = "*"
sphinx = "~=3.3"
sphinx_rtd_theme = "*"
tox = "*"
Generated
+18 -18
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "abc7e5f5a8d075d4b013ceafd06ca07f57e597f053d670f73449ba210511b114"
"sha256": "ae2643b9cf0cf5741ae149fb6bc0c480de41329ce48e773eb4b5d760bc5e2244"
},
"pipfile-spec": 6,
"requires": {},
@@ -105,14 +105,6 @@
"index": "pypi",
"version": "==3.12.2"
},
"filemagic": {
"hashes": [
"sha256:b2fd77411975510e28673220c4b8868ed81b5eb5906339b6f4c233b32122d7d3",
"sha256:e684359ef40820fe406f0ebc5bf8a78f89717bdb7fed688af68082d991d6dbf3"
],
"index": "pypi",
"version": "==1.6"
},
"fuzzywuzzy": {
"hashes": [
"sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8",
@@ -131,11 +123,11 @@
},
"imap-tools": {
"hashes": [
"sha256:070929b8ec429c0aad94588a37a2962eed656a119ab61dcf91489f20fe983f5d",
"sha256:6232cd43748741496446871e889eb137351fc7a7e7f4c7888cd8c0fa28e20cda"
"sha256:96e9a4ff6483462635737730a1df28e739faa71967b12a84f4363fb386542246",
"sha256:a3ee1827dc4ff185b259b33d0238b091a87d489f63ee59959fcc81716456c602"
],
"index": "pypi",
"version": "==0.31.0"
"version": "==0.32.0"
},
"joblib": {
"hashes": [
@@ -337,6 +329,14 @@
"index": "pypi",
"version": "==0.12.0"
},
"python-magic": {
"hashes": [
"sha256:356efa93c8899047d1eb7d3eb91e871ba2f5b1376edbaf4cc305e3c872207355",
"sha256:b757db2a5289ea3f1ced9e60f072965243ea43a2221430048fd8cacab17be0ce"
],
"index": "pypi",
"version": "==0.4.18"
},
"pytz": {
"hashes": [
"sha256:3e6b7dd2d1e0a59084bcee14a17af60c5c562cdc16d828e8eba2e683d3a7e268",
@@ -617,11 +617,11 @@
},
"coveralls": {
"hashes": [
"sha256:4430b862baabb3cf090d36d84d331966615e4288d8a8c5957e0fd456d0dd8bd6",
"sha256:b3b60c17b03a0dee61952a91aed6f131e0b2ac8bd5da909389c53137811409e1"
"sha256:2301a19500b06649d2ec4f2858f9c69638d7699a4c63027c5d53daba666147cc",
"sha256:b990ba1f7bc4288e63340be0433698c1efe8217f78c689d254c2540af3d38617"
],
"index": "pypi",
"version": "==2.1.2"
"version": "==2.2.0"
},
"distlib": {
"hashes": [
@@ -663,11 +663,11 @@
},
"faker": {
"hashes": [
"sha256:4d038ba51ae5e0a956d79cadd684d856e5750bfd608b61dad1807f8f08b1da49",
"sha256:f260f0375a44cd1e1a735c9b8c9b914304f607b5eef431d20e098c7c2f5b50a6"
"sha256:3f5d379e4b5ce92a8afe3c2ce59d7c43886370dd3bf9495a936b91888debfc81",
"sha256:8c0e8a06acef4b9312902e2ce18becabe62badd3a6632180bd0680c6ee111473"
],
"markers": "python_version >= '3.5'",
"version": "==4.16.0"
"version": "==4.17.0"
},
"filelock": {
"hashes": [
+34
View File
@@ -15,8 +15,42 @@ map_uidgid() {
fi
}
wait_for_postgres() {
attempt_num=1
max_attempts=5
echo "Waiting for PostgreSQL to start..."
host="${PAPERLESS_DBHOST}"
while !</dev/tcp/$host/5432 ;
do
if [ $attempt_num -eq $max_attempts ]
then
echo "Unable to connect to database."
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(expr "$attempt_num" + 1)
sleep 5
done
}
migrations() {
if [[ -n "${PAPERLESS_DBHOST}" ]]
then
wait_for_postgres
fi
(
# flock is in place to prevent multiple containers from doing migrations
# simultaneously. This also ensures that the db is ready when the command
+6
View File
@@ -1,3 +1,6 @@
[inet_http_server] ; inet (TCP) server disabled by default
port=127.0.0.1:9001 ; ip_address:port specifier, *:port for all iface
[supervisord]
nodaemon=true ; start in foreground if true; default false
logfile=/var/log/supervisord/supervisord.log ; main log file; default $CWD/supervisord.log
@@ -7,6 +10,9 @@ logfile_backups=10 ; # of main logfile backups; 0 means none, default
loglevel=info ; log level; default info; others: debug,warn,trace
user=root
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[program:gunicorn]
command=gunicorn -c /usr/src/paperless/gunicorn.conf.py -b 0.0.0.0:8000 paperless.wsgi
user=paperless
+31 -11
View File
@@ -5,20 +5,40 @@
Changelog
*********
next
####
* Major changes to the front end (colors, shadows, layout of the cards,
better mobile support)
* Paperless now uses mime types and libmagic detection to determine
if a file type is supported and which parser to use. Removes all
file type checks that where present in MANY different places in
paperless.
* Mail consumer now correctly consumes documents even when their
content type was not set correctly. (i.e. PDF documents with
content type ``application/octet-stream``)
* Basic sorting of mail rules added
* Much better admin for mail rule editing.
* Docker entrypoint script awaits the database server if it is
configured.
* Disabled editing of logs.
* New setting ``PAPERLESS_OCR_PAGES`` limits the tesseract parser
to the first n pages of scanned documents.
* Fixed a bug where tasks with too long task names would not show
up in the admin.
paperless-ng 0.9.1
##################
* Moved documentation of the settings to the actual documentation.
* Updated release script to force the user to choose between SQLite
and PostgreSQL. This avoids confusion when upgrading from paperless.
paperless-ng 0.9.0
##################
* **Deprecated:** GnuPG. :ref:`See this note on the state of GnuPG in paperless-ng. <utilities-encyption>`
This features will most likely be removed in future versions.
* Moved documentation of the settings VG271UP
* **Added:** New frontend. Features:
* Single page application: It's much more responsive than the django admin pages.
+10
View File
@@ -184,6 +184,16 @@ PAPERLESS_TIME_ZONE=<timezone>
PAPERLESS_OCR_PAGES=<num>
Tells paperless to use only the specified amount of pages for OCR. Documents
with less than the specified amount of pages get OCR'ed completely.
Specifying 1 here will only use the first page.
Defaults to 0, which disables this feature and always uses all pages.
PAPERLESS_OCR_LANGUAGE=<lang>
Customize the default language that tesseract will attempt to use when
parsing documents. The default language is used whenever
+11
View File
@@ -21,6 +21,17 @@ is
files around manually. This folder is meant to be entirely managed by docker
and paperless.
**Q:** *What file types does paperless-ng support?*
**A:** Currently, the following files are supported:
* PDF documents, PNG images and JPEG images are processed with OCR.
* Plain text documents are supported as well and are added verbatim
to paperless.
Paperless determines the type of a file by inspecting its content. The
file extensions do not matter.
**Q:** *Will paperless-ng run on Raspberry Pi?*
**A:** The short answer is yes. I've tested it on a Raspberry Pi 3 B.
+3
View File
@@ -44,6 +44,9 @@ resources in the documentation:
that's fully tested and production ready.
* See :ref:`this note <utilities-encyption>` about GnuPG encryption in
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.
* The :ref:`changelog <paperless_changelog>` contains a detailed list of all changes
in paperless-ng.
+120 -15
View File
@@ -10,10 +10,10 @@ Go to the project page on GitHub and download the
`latest release <https://github.com/jonaswinkler/paperless-ng/releases>`_.
There are multiple options available.
* Download the docker-compose files if you want to pull paperless from
* Download the dockerfiles archive if you want to pull paperless from
Docker Hub.
* Download the archive and extract it if you want to build the docker image
* Download the dist archive and extract it if you want to build the docker image
yourself or want to install paperless without docker.
.. hint::
@@ -22,6 +22,15 @@ There are multiple options available.
is not to pull the entire git repository. Paperless-ng includes artifacts
that need to be compiled, and that's already done for you in the release.
.. admonition:: Want to try out paperless-ng before migrating?
The release contains a file ``.env`` which sets the docker-compose project
name to "paperless", which is the same as before and instructs docker-compose
to reuse and upgrade your paperless volumes.
Just rename the project name in that file to anything else and docker-compose
will create fresh volumes for you!
Overview of Paperless-ng
########################
@@ -57,6 +66,8 @@ Paperless consists of the following components:
$ cd /path/to/paperless/src/
$ pipenv run python3 manage.py document_consumer
.. _setup-task_processor:
* **The task processor:** Paperless relies on `Django Q <https://django-q.readthedocs.io/en/latest/>`_
for doing much of the heavy lifting. This is a task queue that accepts tasks from
multiple sources and processes tasks in parallel. It also comes with a scheduler that executes
@@ -77,7 +88,8 @@ Paperless consists of the following components:
a modern multicore system, consumption with full ocr is blazing fast.
The task processor comes with a built-in admin interface that you can use to see whenever any of the
tasks fail and inspect the errors.
tasks fail and inspect the errors (i.e., wrong email credentials, errors during consuming a specific
file, etc).
You may start the task processor by executing:
@@ -240,15 +252,21 @@ Migration to paperless-ng is then performed in a few simple steps:
.. caution::
Make sure you also download the ``.env`` file. This will set the
project name for docker compose to ``paperless`` and then it will
automatically reuse your existing paperless volumes.
The release include a ``.env`` file. This will set the
project name for docker compose to ``paperless`` so that paperless-ng will
automatically reuse your existing paperless volumes. When you start it, it
will migrate your existing data. After that, your old paperless installation
will be incompatible with the migrated volumes.
4. Adjust ``docker-compose.yml`` and
4. Copy the ``docker-compose.sqlite.yml`` file to ``docker-compose.yml``.
If you want to migrate to PostgreSQL, do that after you migrated your existing
SQLite database.
5. Adjust ``docker-compose.yml`` and
``docker-compose.env`` to your needs.
See `docker route`_ for details on which edits are required.
See `docker route`_ for details on which edits are advised.
5. Start paperless-ng.
6. Start paperless-ng.
.. code:: bash
@@ -264,19 +282,106 @@ Migration to paperless-ng is then performed in a few simple steps:
This will run paperless in the background and automatically start it on system boot.
6. Paperless installed a permanent redirect to ``admin/`` in your browser. This
7. Paperless installed a permanent redirect to ``admin/`` in your browser. This
redirect is still in place and prevents access to the new UI. Clear
everything related to paperless in your browsers data in order to fix
this issue.
browsing cache in order to fix this.
8. Optionally, follow the instructions below to migrate your existing data to PostgreSQL.
.. _setup-sqlite_to_psql:
Moving data from sqlite to postgresql
Moving data from SQLite to PostgreSQL
=====================================
.. warning::
Moving your data from SQLite to PostgreSQL is done via executing a series of django
management commands as below.
.. caution::
Make sure that your sqlite database is migrated to the latest version.
Starting paperless will make sure that this is the case. If your try to
load data from an old database schema in SQLite into a newer database
schema in PostgreSQL, you will run into trouble.
1. Stop paperless, if it is running.
2. Tell paperless to use PostgreSQL:
a) With docker, copy the provided ``docker-compose.postgres.yml`` file to
``docker-compose.yml``. Remember to adjust the consumption directory,
if necessary.
b) Without docker, configure the database in your ``paperless.conf`` file.
See :ref:`configuration` for details.
3. Open a shell and initialize the database:
a) With docker, run the following command to open a shell within the paperless
container:
.. code:: shell-session
$ cd /path/to/paperless
$ docker-compose run --rm webserver /bin/bash
This will lauch the container and initialize the PostgreSQL database.
b) Without docker, open a shell in your virtual environment, switch to
the ``src`` directory and create the database schema:
.. code:: shell-session
$ cd /path/to/paperless
$ pipenv shell
$ cd src
$ python3 manage.py migrate
This will not copy any data yet.
4. Dump your data from SQLite:
.. code:: shell-session
$ python3 manage.py dumpdata --database=sqlite --exclude=contenttypes --exclude=auth.Permission > data.json
5. Load your data into PostgreSQL:
.. code:: shell-session
$ python3 manage.py loaddata data.json
6. Exit the shell.
.. code:: shell-session
$ exit
7. Start paperless.
Considerations for less powerful devices
########################################
Paperless runs on Raspberry Pi. However, some things are rather slow on the Pi and
configuring some options in paperless can help improve performance immensely:
* Consider setting ``PAPERLESS_OCR_PAGES`` to 1, so that paperless will only OCR
the first page of your documents.
* ``PAPERLESS_TASK_WORKERS`` and ``PAPERLESS_THREADS_PER_WORKER`` are configured
to use all cores. The Raspberry Pi models 3 and up have 4 cores, meaning that
paperless will use 2 workers and 2 threads per worker. This may result in
slugish response times during consumption, so you might want to lower these
settings (example: 2 workers and 1 thread to always have some computing power
left for other tasks).
* Keep ``PAPERLESS_OCR_ALWAYS`` at its default value 'false' and consider OCR'ing
your documents before feeding them into paperless. Some scanners are able to
do this!
* Lower ``PAPERLESS_CONVERT_DENSITY`` from its default value 300 to 200. This
will still result in rather accurate OCR, but will decrease consumption time
by quite a bit.
* Set ``PAPERLESS_OPTIMIZE_THUMBNAILS`` to 'false' if you want faster consumption
times. Thumbnails will be about 20% larger.
For details, refer to :ref:`configuration`.
TBD.
.. _redis: https://redis.io/
+1
View File
@@ -35,6 +35,7 @@
#PAPERLESS_TASK_WORKERS=1
#PAPERLESS_THREADS_PER_WORKER=1
#PAPERLESS_TIME_ZONE=UTC
#PAPERLESS_OCR_PAGES=1
#PAPERLESS_OCR_LANGUAGE=eng
#PAPERLESS_OCR_ALWAYS=false
#PAPERLESS_CONSUMER_POLLING=10
+6 -3
View File
@@ -17,6 +17,7 @@ PAPERLESS_ROOT=$(git rev-parse --show-toplevel)
# output directory
PAPERLESS_DIST="$PAPERLESS_ROOT/dist"
PAPERLESS_DIST_APP="$PAPERLESS_DIST/paperless-ng"
PAPERLESS_DIST_DOCKERFILES="$PAPERLESS_DIST/paperless-ng-dockerfiles"
if [ -d "$PAPERLESS_DIST" ]
then
@@ -27,6 +28,7 @@ fi
mkdir "$PAPERLESS_DIST"
mkdir "$PAPERLESS_DIST_APP"
mkdir "$PAPERLESS_DIST_APP/docker"
mkdir "$PAPERLESS_DIST_DOCKERFILES"
# setup dependencies.
@@ -78,9 +80,9 @@ cp "$PAPERLESS_ROOT/docker/local/"* "$PAPERLESS_DIST_APP"
cp "$PAPERLESS_ROOT/docker/docker-compose.env" "$PAPERLESS_DIST_APP"
# docker files for pulling from docker hub
cp "$PAPERLESS_ROOT/docker/hub/"* "$PAPERLESS_DIST"
cp "$PAPERLESS_ROOT/.env" "$PAPERLESS_DIST"
cp "$PAPERLESS_ROOT/docker/docker-compose.env" "$PAPERLESS_DIST"
cp "$PAPERLESS_ROOT/docker/hub/"* "$PAPERLESS_DIST_DOCKERFILES"
cp "$PAPERLESS_ROOT/.env" "$PAPERLESS_DIST_DOCKERFILES"
cp "$PAPERLESS_ROOT/docker/docker-compose.env" "$PAPERLESS_DIST_DOCKERFILES"
# auxiliary files required for the docker image
cp "$PAPERLESS_ROOT/docker/docker-entrypoint.sh" "$PAPERLESS_DIST_APP/docker/"
@@ -99,3 +101,4 @@ docker build . -t "jonaswinkler/paperless-ng:$VERSION"
cd "$PAPERLESS_DIST"
tar -cJf "paperless-ng-$VERSION.tar.xz" paperless-ng/
tar -cJf "paperless-ng-$VERSION-dockerfiles.tar.xz" paperless-ng-dockerfiles/
+129 -125
View File
@@ -1,126 +1,130 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"paperless-ui": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/paperless-ui",
"outputHashing": "none",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "paperless-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
}
}
}},
"defaultProject": "paperless-ui"
}
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"paperless-ui": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/paperless-ui",
"outputHashing": "none",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "paperless-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
}
}
}
},
"defaultProject": "paperless-ui"
}
File renamed without changes.
+1 -1
View File
@@ -3,7 +3,7 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
styleUrls: ['./app.component.scss']
})
export class AppComponent {
+9 -1
View File
@@ -41,6 +41,10 @@ import { TagsComponent } from './components/common/input/tags/tags.component';
import { SortableDirective } from './directives/sortable.directive';
import { CookieService } from 'ngx-cookie-service';
import { CsrfInterceptor } from './interceptors/csrf.interceptor';
import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-view-widget/saved-view-widget.component';
import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component';
import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component';
import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component';
@NgModule({
declarations: [
@@ -74,7 +78,11 @@ import { CsrfInterceptor } from './interceptors/csrf.interceptor';
SaveViewConfigDialogComponent,
DateTimeComponent,
TagsComponent,
SortableDirective
SortableDirective,
SavedViewWidgetComponent,
StatisticsWidgetComponent,
UploadFileWidgetComponent,
WidgetFrameComponent
],
imports: [
BrowserModule,
@@ -1,32 +1,26 @@
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<span class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Paperless-ng</span>
<nav class="navbar navbar-dark sticky-top bg-primary flex-md-nowrap p-0 shadow">
<span class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">
<img src="assets/logo-dark-notext.svg" height="18px" class="mr-2">
Paperless-ng
</span>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"
(click)="isMenuCollapsed = !isMenuCollapsed">
<span class="navbar-toggler-icon"></span>
</button>
<form (ngSubmit)="search()" class="w-100">
<form (ngSubmit)="search()" class="w-100 m-1">
<input class="form-control form-control-dark" type="text" placeholder="Search" aria-label="Search"
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (selectItem)="itemSelected($event)">
</form>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="accounts/logout/">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#door-closed"/>
</svg>
Logout
</a>
</li>
</ul>
</nav>
<div class="container-fluid">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse" [ngbCollapse]="isMenuCollapsed">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active">
<a class="nav-link" routerLink="dashboard" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#house"/>
</svg>
@@ -34,7 +28,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="documents" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">
<a class="nav-link" routerLink="documents" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#files"/>
</svg>
@@ -48,7 +42,7 @@
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item w-100" *ngFor='let config of viewConfigService.getSideBarConfigs()'>
<a class="nav-link text-truncate" routerLink="view/{{config.id}}" routerLinkActive="active">
<a class="nav-link text-truncate" routerLink="view/{{config.id}}" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#funnel"/>
</svg>
@@ -62,7 +56,7 @@
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item w-100" *ngFor='let d of openDocuments'>
<a class="nav-link text-truncate" routerLink="documents/{{d.id}}" routerLinkActive="active">
<a class="nav-link text-truncate" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
</svg>
@@ -84,7 +78,7 @@
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link" routerLink="correspondents" routerLinkActive="active">
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person"/>
</svg>
@@ -92,7 +86,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="tags" routerLinkActive="active">
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
</svg>
@@ -100,7 +94,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active">
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
</svg>
@@ -108,7 +102,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="logs" routerLinkActive="active">
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
</svg>
@@ -116,7 +110,7 @@
</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="settings" routerLinkActive="active">
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
</svg>
@@ -150,7 +144,15 @@
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#link"/>
</svg>
Github
GitHub
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="accounts/logout/">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#door-open"/>
</svg>
Logout
</a>
</li>
</ul>
@@ -161,4 +163,4 @@
<router-outlet></router-outlet>
</main>
</div>
</div>
</div>
@@ -1,4 +1,6 @@
@import "/src/theme";
/*
* Sidebar
*/
@@ -15,14 +17,15 @@
@media (max-width: 767.98px) {
.sidebar {
top: 5rem;
top: 3rem;
}
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
/* height: calc(100vh - 48px); */
height: 100%;
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
@@ -46,7 +49,7 @@
}
.sidebar .nav-link.active {
color: #007bff;
color: $primary;
}
.sidebar .nav-link:hover .sidebaricon,
@@ -12,7 +12,7 @@ import { DocumentDetailComponent } from '../document-detail/document-detail.comp
@Component({
selector: 'app-app-frame',
templateUrl: './app-frame.component.html',
styleUrls: ['./app-frame.component.css']
styleUrls: ['./app-frame.component.scss']
})
export class AppFrameComponent implements OnInit, OnDestroy {
@@ -25,6 +25,12 @@ export class AppFrameComponent implements OnInit, OnDestroy {
) {
}
isMenuCollapsed: boolean = true
closeMenu() {
this.isMenuCollapsed = true
}
searchField = new FormControl('')
openDocuments: PaperlessDocument[] = []
@@ -61,10 +67,12 @@ export class AppFrameComponent implements OnInit, OnDestroy {
}
search() {
this.closeMenu()
this.router.navigate(['search'], {queryParams: {query: this.searchField.value}})
}
closeAll() {
this.closeMenu()
this.openDocumentsService.closeAll()
// TODO: is there a better way to do this?
@@ -4,7 +4,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-delete-dialog',
templateUrl: './delete-dialog.component.html',
styleUrls: ['./delete-dialog.component.css']
styleUrls: ['./delete-dialog.component.scss']
})
export class DeleteDialogComponent implements OnInit {
@@ -1,5 +1,5 @@
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" [id]="inputId" [(ngModel)]="value" (change)="onChange(value)" (blur)="onTouched()" [disabled]="disabled">
<label class="form-check-label" [for]="inputId">{{title}}</label>
<div class="form-group custom-control custom-checkbox">
<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>
@@ -11,7 +11,7 @@ import { AbstractInputComponent } from '../abstract-input';
}],
selector: 'app-input-check',
templateUrl: './check.component.html',
styleUrls: ['./check.component.css']
styleUrls: ['./check.component.scss']
})
export class CheckComponent extends AbstractInputComponent<boolean> {
@@ -11,7 +11,7 @@ import { AbstractInputComponent } from '../abstract-input';
}],
selector: 'app-input-date-time',
templateUrl: './date-time.component.html',
styleUrls: ['./date-time.component.css']
styleUrls: ['./date-time.component.scss']
})
export class DateTimeComponent implements OnInit,ControlValueAccessor {
@@ -10,7 +10,7 @@ import { AbstractInputComponent } from '../abstract-input';
}],
selector: 'app-input-select',
templateUrl: './select.component.html',
styleUrls: ['./select.component.css']
styleUrls: ['./select.component.scss']
})
export class SelectComponent extends AbstractInputComponent<number> {
@@ -15,7 +15,7 @@ import { TagService } from 'src/app/services/rest/tag.service';
}],
selector: 'app-input-tags',
templateUrl: './tags.component.html',
styleUrls: ['./tags.component.css']
styleUrls: ['./tags.component.scss']
})
export class TagsComponent implements OnInit, ControlValueAccessor {
@@ -11,7 +11,7 @@ import { AbstractInputComponent } from '../abstract-input';
}],
selector: 'app-input-text',
templateUrl: './text.component.html',
styleUrls: ['./text.component.css']
styleUrls: ['./text.component.scss']
})
export class TextComponent extends AbstractInputComponent<string> {
@@ -1,6 +1,7 @@
<div class="row pt-3 pb-1 mb-3 border-bottom align-items-center" >
<div class="col text-truncate">
<h1 class="h2 text-truncate" style="line-height: 1.4">{{title}}</h1>
<div class="col-md text-truncate">
<p class="h2 text-truncate" style="line-height: 1.4">{{title}}</p>
<p *ngIf="subTitle" class="h5 text-truncate" style="line-height: 1.4">{{subTitle}}</p>
</div>
<div class="btn-toolbar col-auto">
<ng-content></ng-content>
@@ -3,7 +3,7 @@ import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-page-header',
templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.css']
styleUrls: ['./page-header.component.scss']
})
export class PageHeaderComponent implements OnInit {
@@ -12,6 +12,9 @@ export class PageHeaderComponent implements OnInit {
@Input()
title: string = ""
@Input()
subTitle: string = ""
ngOnInit(): void {
}
@@ -4,7 +4,7 @@ import { TAG_COLOURS, PaperlessTag } from 'src/app/data/paperless-tag';
@Component({
selector: 'app-tag',
templateUrl: './tag.component.html',
styleUrls: ['./tag.component.css']
styleUrls: ['./tag.component.scss']
})
export class TagComponent implements OnInit {
@@ -5,7 +5,7 @@ import { Toast, ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-toasts',
templateUrl: './toasts.component.html',
styleUrls: ['./toasts.component.css']
styleUrls: ['./toasts.component.scss']
})
export class ToastsComponent implements OnInit, OnDestroy {
@@ -1,50 +1,25 @@
<app-page-header title="Dashboard">
<app-page-header title="Dashboard" subTitle="Welcome to paperless-ng!">
<img src="assets/logo.svg" height="80" class="m-2 d-none d-md-block">
</app-page-header>
<p>Welcome to paperless-ng!</p>
<div class='row'>
<div class="col-lg">
<ng-container *ngFor="let v of savedDashboardViews">
<h4>{{v.viewConfig.title}}</h4>
<app-widget-frame title="Saved views" *ngIf="savedViews.length == 0">
<p class="card-text">This space is reserved to display your saved views. Go to your documents and save a view
to have it displayed
here!</p>
</app-widget-frame>
<table class="table table-sm table-hover table-borderless">
<thead>
<tr>
<th>Created</th>
<th scope="col">Title</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let doc of v.documents" routerLink="/documents/{{doc.id}}">
<td>{{doc.created | date}}</td>
<td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags" class="ml-1"></app-tag>
</tr>
</tbody>
</table>
</ng-container>
<ng-container *ngIf="savedDashboardViews.length == 0">
<h4>Saved views</h4>
<p>This space is reserved to display your saved views. Go to your documents and save a view to have it displayed here!</p>
<ng-container *ngFor="let v of savedViews">
<app-saved-view-widget [savedView]="v"></app-saved-view-widget>
</ng-container>
</div>
<div class="col-lg">
<h4>Statistics</h4>
<p>Documents in inbox: {{statistics.documents_inbox}}</p>
<p>Total documents: {{statistics.documents_total}}</p>
<h4>Upload new Document</h4>
<form>
<ngx-file-drop
dropZoneLabel="Drop documents here"
(onFileDrop)="dropped($event)"
(onFileOver)="fileOver($event)"
(onFileLeave)="fileLeave($event)"
dropZoneClassName="bg-light mt-4 card">
</ngx-file-drop>
</form>
<app-statistics-widget></app-statistics-widget>
<app-upload-file-widget></app-upload-file-widget>
</div>
</div>
</div>
@@ -1,71 +1,22 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
import { Observable } from 'rxjs';
import { DocumentService } from 'src/app/services/rest/document.service';
import { SavedViewConfigService } from 'src/app/services/saved-view-config.service';
import { Toast, ToastService } from 'src/app/services/toast.service';
import { environment } from 'src/environments/environment';
export interface Statistics {
documents_total?: number
documents_inbox?: number
}
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit {
constructor(private documentService: DocumentService, private toastService: ToastService,
public savedViewConfigService: SavedViewConfigService, private http: HttpClient) { }
constructor(
public savedViewConfigService: SavedViewConfigService) { }
savedDashboardViews = []
statistics: Statistics = {}
savedViews = []
ngOnInit(): void {
this.savedViewConfigService.getDashboardConfigs().forEach(config => {
this.documentService.list(1,10,config.sortField,config.sortDirection,config.filterRules).subscribe(result => {
this.savedDashboardViews.push({viewConfig: config, documents: result.results})
})
})
this.getStatistics().subscribe(statistics => {
this.statistics = statistics
})
this.savedViews = this.savedViewConfigService.getDashboardConfigs()
}
getStatistics(): Observable<Statistics> {
return this.http.get(`${environment.apiBaseUrl}statistics/`)
}
public fileOver(event){
console.log(event);
}
public fileLeave(event){
console.log(event);
}
public dropped(files: NgxFileDropEntry[]) {
for (const droppedFile of files) {
if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
console.log(fileEntry)
fileEntry.file((file: File) => {
console.log(file)
const formData = new FormData()
formData.append('document', file, file.name)
this.documentService.uploadDocument(formData).subscribe(result => {
this.toastService.showToast(Toast.make("Information", "The document has been uploaded and will be processed by the consumer shortly."))
}, error => {
this.toastService.showToast(Toast.makeError("An error has occured while uploading the document. Sorry!"))
})
});
}
}
}
}
@@ -0,0 +1,18 @@
<app-widget-frame [title]="savedView.title">
<table class="table table-sm table-hover table-borderless">
<thead>
<tr>
<th>Created</th>
<th scope="col">Title</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let doc of documents" routerLink="/documents/{{doc.id}}">
<td>{{doc.created | date}}</td>
<td>{{doc.title}}<app-tag [tag]="t" *ngFor="let t of doc.tags" class="ml-1"></app-tag>
</tr>
</tbody>
</table>
</app-widget-frame>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SavedViewWidgetComponent } from './saved-view-widget.component';
describe('SavedViewWidgetComponent', () => {
let component: SavedViewWidgetComponent;
let fixture: ComponentFixture<SavedViewWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SavedViewWidgetComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SavedViewWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,26 @@
import { Component, Input, OnInit } from '@angular/core';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { SavedViewConfig } from 'src/app/data/saved-view-config';
import { DocumentService } from 'src/app/services/rest/document.service';
@Component({
selector: 'app-saved-view-widget',
templateUrl: './saved-view-widget.component.html',
styleUrls: ['./saved-view-widget.component.scss']
})
export class SavedViewWidgetComponent implements OnInit {
constructor(private documentService: DocumentService) { }
@Input()
savedView: SavedViewConfig
documents: PaperlessDocument[] = []
ngOnInit(): void {
this.documentService.list(1,10,this.savedView.sortField,this.savedView.sortDirection,this.savedView.filterRules).subscribe(result => {
this.documents = result.results
})
}
}
@@ -0,0 +1,4 @@
<app-widget-frame title="Statistics">
<p class="card-text">Documents in inbox: {{statistics.documents_inbox}}</p>
<p class="card-text">Total documents: {{statistics.documents_total}}</p>
</app-widget-frame>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StatisticsWidgetComponent } from './statistics-widget.component';
describe('StatisticsWidgetComponent', () => {
let component: StatisticsWidgetComponent;
let fixture: ComponentFixture<StatisticsWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ StatisticsWidgetComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(StatisticsWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,33 @@
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
export interface Statistics {
documents_total?: number
documents_inbox?: number
}
@Component({
selector: 'app-statistics-widget',
templateUrl: './statistics-widget.component.html',
styleUrls: ['./statistics-widget.component.scss']
})
export class StatisticsWidgetComponent implements OnInit {
constructor(private http: HttpClient) { }
statistics: Statistics = {}
getStatistics(): Observable<Statistics> {
return this.http.get(`${environment.apiBaseUrl}statistics/`)
}
ngOnInit(): void {
this.getStatistics().subscribe(statistics => {
this.statistics = statistics
})
}
}
@@ -0,0 +1,15 @@
<app-widget-frame title="Upload new documents">
<form>
<ngx-file-drop
dropZoneLabel="Drop documents here or" (onFileDrop)="dropped($event)"
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)"
dropZoneClassName="bg-light card"
multiple="true"
contentClassName="justify-content-center d-flex align-items-center p-5"
[showBrowseBtn]=true
browseBtnClassName="btn btn-sm btn-outline-primary ml-2">
</ngx-file-drop>
</form>
</app-widget-frame>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UploadFileWidgetComponent } from './upload-file-widget.component';
describe('UploadFileWidgetComponent', () => {
let component: UploadFileWidgetComponent;
let fixture: ComponentFixture<UploadFileWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UploadFileWidgetComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UploadFileWidgetComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,44 @@
import { Component, OnInit } from '@angular/core';
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop';
import { DocumentService } from 'src/app/services/rest/document.service';
import { Toast, ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-upload-file-widget',
templateUrl: './upload-file-widget.component.html',
styleUrls: ['./upload-file-widget.component.scss']
})
export class UploadFileWidgetComponent implements OnInit {
constructor(private documentService: DocumentService, private toastService: ToastService) { }
ngOnInit(): void {
}
public fileOver(event){
console.log(event);
}
public fileLeave(event){
console.log(event);
}
public dropped(files: NgxFileDropEntry[]) {
for (const droppedFile of files) {
if (droppedFile.fileEntry.isFile) {
const fileEntry = droppedFile.fileEntry as FileSystemFileEntry;
console.log(fileEntry)
fileEntry.file((file: File) => {
console.log(file)
const formData = new FormData()
formData.append('document', file, file.name)
this.documentService.uploadDocument(formData).subscribe(result => {
this.toastService.showToast(Toast.make("Information", "The document has been uploaded and will be processed by the consumer shortly."))
}, error => {
this.toastService.showToast(Toast.makeError("An error has occured while uploading the document. Sorry!"))
})
});
}
}
}
}
@@ -0,0 +1,8 @@
<div class="card mb-3 shadow">
<div class="card-header">
<h5 class="card-title mb-0">{{title}}</h5>
</div>
<div class="card-body text-dark">
<ng-content></ng-content>
</div>
</div>
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WidgetFrameComponent } from './widget-frame.component';
describe('WidgetFrameComponent', () => {
let component: WidgetFrameComponent;
let fixture: ComponentFixture<WidgetFrameComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ WidgetFrameComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(WidgetFrameComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-widget-frame',
templateUrl: './widget-frame.component.html',
styleUrls: ['./widget-frame.component.scss']
})
export class WidgetFrameComponent implements OnInit {
constructor() { }
@Input()
title: string
ngOnInit(): void {
}
}
@@ -3,19 +3,19 @@
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>
<span class="d-none d-lg-inline">Delete</span>
<span class="d-none d-lg-inline"> Delete</span>
</button>
<a [href]="downloadUrl" class="btn btn-sm btn-outline-secondary mr-2">
<a [href]="downloadUrl" class="btn btn-sm btn-outline-primary mr-2">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#download" />
</svg>
<span class="d-none d-lg-inline">Download</span>
<span class="d-none d-lg-inline"> Download</span>
</a>
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="close()">
<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" />
</svg>
<span class="d-none d-lg-inline">Close</span>
<span class="d-none d-lg-inline"> Close</span>
</button>
</app-page-header>
@@ -23,17 +23,17 @@
<div class="row">
<div class="col-xl">
<form [formGroup]='documentForm' (ngSubmit)="save()">
<app-input-text title="Title" formControlName="title"></app-input-text>
<div class="form-group">
<label for="archive_serial_number">Archive Serial Number</label>
<input type="number" class="form-control" id="archive_serial_number"
formControlName='archive_serial_number'>
</div>
<app-input-date-time title="Date created" titleTime="Time created" formControlName="created"></app-input-date-time>
<div class="form-group">
<label for="content">Content</label>
<textarea class="form-control" id="content" rows="5" formControlName='content'></textarea>
@@ -58,4 +58,4 @@
</object>
</div>
</div>
</div>
@@ -21,7 +21,7 @@ import { TagEditDialogComponent } from '../manage/tag-list/tag-edit-dialog/tag-e
@Component({
selector: 'app-document-detail',
templateUrl: './document-detail.component.html',
styleUrls: ['./document-detail.component.css']
styleUrls: ['./document-detail.component.scss']
})
export class DocumentDetailComponent implements OnInit {
@@ -1,7 +1,7 @@
<div class="card mb-3 bg-light">
<div class="card mb-3 bg-light shadow-sm">
<div class="row no-gutters">
<div class="col-md-2 d-none d-lg-block">
<img [src]="getThumbUrl()" class="card-img doc-img">
<img [src]="getThumbUrl()" class="card-img doc-img border-right">
</div>
<div class="col">
<div class="card-body">
@@ -6,7 +6,7 @@ import { DocumentService } from 'src/app/services/rest/document.service';
@Component({
selector: 'app-document-card-large',
templateUrl: './document-card-large.component.html',
styleUrls: ['./document-card-large.component.css']
styleUrls: ['./document-card-large.component.scss']
})
export class DocumentCardLargeComponent implements OnInit {
@@ -1,5 +0,0 @@
.doc-img {
object-fit: cover;
object-position: top;
}
@@ -1,11 +1,20 @@
<div class="col-auto mb-3">
<div class="card h-100 bg-light" style="width: 14rem">
<div style="height: 10rem; overflow: hidden;">
<img [src]="getThumbUrl()" class="card-img doc-img"/>
<div class="col p-2 h-100" style="width: 16rem;">
<div class="card h-100 shadow-sm">
<div class=" border-bottom doc-img pr-1" [ngStyle]="{'background-image': 'url(' + getThumbUrl() + ')'}">
<div class="row" *ngFor="let t of document.tags">
<app-tag [tag]="t" class="col text-right"></app-tag>
</div>
</div>
<div class="card-body">
<p class="card-title">{{document.correspondent ? document.correspondent.name + ': ' : ''}}{{document.title}} <app-tag [tag]="t" *ngFor="let t of document.tags" class="ml-1"></app-tag></p>
<div class="d-flex justify-content-between align-items-center">
<div class="card-body p-2">
<p class="card-text">
<span class="font-weight-bold">{{document.correspondent? document.correspondent.name + ': ' : ''}}</span> {{document.title}}
</p>
</div>
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center ml-n2">
<div class="btn-group">
<a routerLink="/documents/{{document.id}}" class="btn btn-sm btn-outline-secondary">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
@@ -21,6 +30,7 @@
</div>
<small class="text-muted">{{document.created | date}}</small>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,5 @@
.doc-img {
background-size: cover;
background-position: top;
height: 200px;
}
@@ -5,7 +5,7 @@ import { DocumentService } from 'src/app/services/rest/document.service';
@Component({
selector: 'app-document-card-small',
templateUrl: './document-card-small.component.html',
styleUrls: ['./document-card-small.component.css']
styleUrls: ['./document-card-small.component.scss']
})
export class DocumentCardSmallComponent implements OnInit {
@@ -1,51 +1,51 @@
<app-page-header [title]="getTitle()">
<div class="btn-group btn-group-toggle mr-2" ngbRadioGroup [(ngModel)]="displayMode"
<div class="btn-group btn-group-toggle" ngbRadioGroup [(ngModel)]="displayMode"
(ngModelChange)="saveDisplayMode()">
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
<label ngbButtonLabel class="btn-outline-primary btn-sm">
<input ngbButton type="radio" class="btn btn-sm" value="details">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#list-ul" />
</svg>
</label>
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
<label ngbButtonLabel class="btn-outline-primary btn-sm">
<input ngbButton type="radio" class="btn btn-sm" value="smallCards">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#grid" />
</svg>
</label>
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
<label ngbButtonLabel class="btn-outline-primary btn-sm">
<input ngbButton type="radio" class="btn btn-sm" value="largeCards">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#hdd-stack" />
</svg>
</label>
</div>
<div class="btn-group btn-group-toggle mr-2" ngbRadioGroup [(ngModel)]="docs.sortDirection"
<div class="btn-group btn-group-toggle ml-2" ngbRadioGroup [(ngModel)]="docs.sortDirection"
*ngIf="!docs.viewId">
<div ngbDropdown class="btn-group">
<button class="btn btn-outline-secondary btn-sm" id="dropdownBasic1" ngbDropdownToggle>Sort by</button>
<button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle>Sort by</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSort(f.field)"
[class.active]="docs.sortField == f.field">{{f.name}}</button>
</div>
</div>
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
<label ngbButtonLabel class="btn-outline-primary btn-sm">
<input ngbButton type="radio" class="btn btn-sm" value="asc">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-down" />
</svg>
</label>
<label ngbButtonLabel class="btn-outline-secondary btn-sm">
<label ngbButtonLabel class="btn-outline-primary btn-sm">
<input ngbButton type="radio" class="btn btn-sm" value="des">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#sort-alpha-up-alt" />
</svg>
</label>
</div>
<div class="btn-group" *ngIf="!docs.viewId">
<div class="btn-group ml-2" *ngIf="!docs.viewId">
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="showFilter=!showFilter">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="showFilter=!showFilter">
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#funnel" />
</svg>
@@ -53,7 +53,7 @@
</button>
<div class="btn-group" ngbDropdown role="group">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle-split" ngbDropdownToggle></button>
<button class="btn btn-sm btn-outline-primary dropdown-toggle-split" ngbDropdownToggle></button>
<div class="dropdown-menu" ngbDropdownMenu>
<button ngbDropdownItem *ngFor="let config of savedViewConfigService.getConfigs()" (click)="loadViewConfig(config)">{{config.title}}</button>
<div class="dropdown-divider" *ngIf="savedViewConfigService.getConfigs().length > 0"></div>
@@ -71,38 +71,40 @@
</div>
</div>
<ngb-pagination [pageSize]="docs.currentPageSize" [collectionSize]="docs.collectionSize" [(page)]="docs.currentPage" [maxSize]="5"
[rotate]="true" [boundaryLinks]="true" (pageChange)="reload()" aria-label="Default pagination"></ngb-pagination>
<div class="row m-0 justify-content-end">
<ngb-pagination [pageSize]="docs.currentPageSize" [collectionSize]="docs.collectionSize" [(page)]="docs.currentPage" [maxSize]="5"
[rotate]="true" (pageChange)="reload()" aria-label="Default pagination"></ngb-pagination>
</div>
<div *ngIf="displayMode == 'largeCards'">
<app-document-card-large *ngFor="let d of docs.documents" [document]="d" [details]="d.content">
</app-document-card-large>
</div>
<table class="table table-hover table-sm" *ngIf="displayMode == 'details'">
<table class="table table-hover table-sm border shadow" *ngIf="displayMode == 'details'">
<thead>
<th>ASN</th>
<th>Correspondent</th>
<th class="d-none d-lg-table-cell">ASN</th>
<th class="d-none d-md-table-cell">Correspondent</th>
<th>Title</th>
<th>Document type</th>
<th class="d-none d-xl-table-cell">Document type</th>
<th>Created</th>
<th>Added</th>
<th class="d-none d-xl-table-cell">Added</th>
</thead>
<tbody>
<tr *ngFor="let d of docs.documents" routerLink="/documents/{{d.id}}">
<td>{{d.archive_serial_number}}</td>
<td>{{d.correspondent ? d.correspondent.name : ''}}</td>
<td>{{d.title}}<app-tag [tag]="t" *ngFor="let t of d.tags" class="ml-1"></app-tag>
</td>
<td>{{d.document_type ? d.document_type.name : ''}}</td>
<td class="d-none d-lg-table-cell">{{d.archive_serial_number}}</td>
<td class="d-none d-md-table-cell">{{d.correspondent ? d.correspondent.name : ''}}</td>
<td>{{d.title}}<app-tag [tag]="t" *ngFor="let t of d.tags" class="ml-1"></app-tag></td>
<td class="d-none d-xl-table-cell">{{d.document_type ? d.document_type.name : ''}}</td>
<td>{{d.created | date}}</td>
<td>{{d.added | date}}</td>
<td class="d-none d-xl-table-cell">{{d.added | date}}</td>
</tr>
</tbody>
</table>
<div class="row justify-content-left" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [document]="d" *ngFor="let d of docs.documents"></app-document-card-small>
<div class=" m-n2 row" *ngIf="displayMode == 'smallCards'">
<app-document-card-small [document]="d" *ngFor="let d of docs.documents"></app-document-card-small>
</div>
<p *ngIf="docs.documents.length == 0" class="mx-auto">No results</p>
@@ -11,7 +11,7 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
@Component({
selector: 'app-document-list',
templateUrl: './document-list.component.html',
styleUrls: ['./document-list.component.css']
styleUrls: ['./document-list.component.scss']
})
export class DocumentListComponent implements OnInit {
@@ -5,7 +5,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
@Component({
selector: 'app-save-view-config-dialog',
templateUrl: './save-view-config-dialog.component.html',
styleUrls: ['./save-view-config-dialog.component.css']
styleUrls: ['./save-view-config-dialog.component.scss']
})
export class SaveViewConfigDialogComponent implements OnInit {
@@ -12,7 +12,7 @@ import { TagService } from 'src/app/services/rest/tag.service';
@Component({
selector: 'app-filter-editor',
templateUrl: './filter-editor.component.html',
styleUrls: ['./filter-editor.component.css']
styleUrls: ['./filter-editor.component.scss']
})
export class FilterEditorComponent implements OnInit {
@@ -9,7 +9,7 @@ import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-correspondent-edit-dialog',
templateUrl: './correspondent-edit-dialog.component.html',
styleUrls: ['./correspondent-edit-dialog.component.css']
styleUrls: ['./correspondent-edit-dialog.component.scss']
})
export class CorrespondentEditDialogComponent extends EditDialogComponent<PaperlessCorrespondent> {
@@ -1,12 +1,14 @@
<app-page-header title="Correspondents">
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="openCreateDialog()">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()">
Create
</button>
</app-page-header>
<ngb-pagination [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
<div class="row m-0 justify-content-end">
<ngb-pagination [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
</div>
<table class="table table-striped">
<table class="table table-striped border shadow">
<thead>
<tr>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
@@ -8,7 +8,7 @@ import { CorrespondentEditDialogComponent } from './correspondent-edit-dialog/co
@Component({
selector: 'app-correspondent-list',
templateUrl: './correspondent-list.component.html',
styleUrls: ['./correspondent-list.component.css']
styleUrls: ['./correspondent-list.component.scss']
})
export class CorrespondentListComponent extends GenericListComponent<PaperlessCorrespondent> {
@@ -9,7 +9,7 @@ import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-document-type-edit-dialog',
templateUrl: './document-type-edit-dialog.component.html',
styleUrls: ['./document-type-edit-dialog.component.css']
styleUrls: ['./document-type-edit-dialog.component.scss']
})
export class DocumentTypeEditDialogComponent extends EditDialogComponent<PaperlessDocumentType> {
@@ -1,13 +1,15 @@
<app-page-header title="Document types">
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="openCreateDialog()">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()">
Create
</button>
</app-page-header>
<ngb-pagination [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()"
<div class="row m-0 justify-content-end">
<ngb-pagination [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()"
aria-label="Default pagination"></ngb-pagination>
</div>
<table class="table table-striped">
<table class="table table-striped border shadow">
<thead>
<tr>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
@@ -8,7 +8,7 @@ import { DocumentTypeEditDialogComponent } from './document-type-edit-dialog/doc
@Component({
selector: 'app-document-type-list',
templateUrl: './document-type-list.component.html',
styleUrls: ['./document-type-list.component.css']
styleUrls: ['./document-type-list.component.scss']
})
export class DocumentTypeListComponent extends GenericListComponent<PaperlessDocumentType> {
@@ -1,7 +1,7 @@
<app-page-header title="Logs">
<div ngbDropdown class="btn-group">
<button class="btn btn-outline-secondary btn-sm" id="dropdownBasic1" ngbDropdownToggle>
<button class="btn btn-outline-primary btn-sm" id="dropdownBasic1" ngbDropdownToggle>
<svg class="toolbaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#funnel" />
</svg>
@@ -6,7 +6,7 @@ import { LogService } from 'src/app/services/rest/log.service';
@Component({
selector: 'app-logs',
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.css']
styleUrls: ['./logs.component.scss']
})
export class LogsComponent implements OnInit {
@@ -57,7 +57,7 @@
</li>
</ul>
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3"></div>
<div [ngbNavOutlet]="nav" class="border-left border-right border-bottom p-3 mb-3 shadow"></div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
@@ -8,7 +8,7 @@ import { SavedViewConfigService } from 'src/app/services/saved-view-config.servi
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.css']
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
@@ -9,7 +9,7 @@ import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-tag-edit-dialog',
templateUrl: './tag-edit-dialog.component.html',
styleUrls: ['./tag-edit-dialog.component.css']
styleUrls: ['./tag-edit-dialog.component.scss']
})
export class TagEditDialogComponent extends EditDialogComponent<PaperlessTag> {
@@ -1,34 +1,37 @@
<app-page-header title="Tags">
<button type="button" class="btn btn-sm btn-outline-secondary" (click)="openCreateDialog()">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()">
Create
</button>
</app-page-header>
<ngb-pagination [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()" aria-label="Default pagination"></ngb-pagination>
<table class="table table-striped">
<thead>
<tr>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
<th scope="col">Colour</th>
<th scope="col" sortable="matching_algorithm" (sort)="onSort($event)">Matching</th>
<th scope="col" sortable="document_count" (sort)="onSort($event)">Document count</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tag of data">
<td scope="row">{{ tag.name }}</td>
<td scope="row"><span class="badge" [style.color]="getColor(tag.colour).textColor" [style.background-color]="getColor(tag.colour).value">{{ getColor(tag.colour).name }}</span></td>
<td scope="row">{{ getMatching(tag) }}</td>
<td scope="row">{{ tag.document_count }}</td>
<td scope="row">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(tag)">Edit</button>
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(tag)">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="row m-0 justify-content-end">
<ngb-pagination [pageSize]="25" [collectionSize]="collectionSize" [(page)]="page" (pageChange)="reloadData()"
aria-label="Default pagination"></ngb-pagination>
</div>
<table class="table table-striped border shadow">
<thead>
<tr>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
<th scope="col">Colour</th>
<th scope="col" sortable="matching_algorithm" (sort)="onSort($event)">Matching</th>
<th scope="col" sortable="document_count" (sort)="onSort($event)">Document count</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let tag of data">
<td scope="row">{{ tag.name }}</td>
<td scope="row"><span class="badge" [style.color]="getColor(tag.colour).textColor"
[style.background-color]="getColor(tag.colour).value">{{ getColor(tag.colour).name }}</span></td>
<td scope="row">{{ getMatching(tag) }}</td>
<td scope="row">{{ tag.document_count }}</td>
<td scope="row">
<div class="btn-group">
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(tag)">Edit</button>
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(tag)">Delete</button>
</div>
</td>
</tr>
</tbody>
</table>
@@ -9,7 +9,7 @@ import { TagEditDialogComponent } from './tag-edit-dialog/tag-edit-dialog.compon
@Component({
selector: 'app-tag-list',
templateUrl: './tag-list.component.html',
styleUrls: ['./tag-list.component.css']
styleUrls: ['./tag-list.component.scss']
})
export class TagListComponent extends GenericListComponent<PaperlessTag> {
Whitespace-only changes.
@@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-not-found',
templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.css']
styleUrls: ['./not-found.component.scss']
})
export class NotFoundComponent implements OnInit {
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff. Show more