Merge branch 'dev' into feature-permissions

This commit is contained in:
Michael Shamoon 2022-12-17 20:05:12 -08:00
commit f31cee75f3
41 changed files with 3562 additions and 1179 deletions

View File

@ -195,6 +195,7 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- run: cd src-ui && npm ci
- run: cd src-ui && npm run lint
- run: cd src-ui && npm run test
- run: cd src-ui && npm run e2e:ci

View File

@ -127,6 +127,7 @@ jobs:
uses: ./.github/workflows/reusable-workflow-builder.yml
with:
dockerfile: ./docker-builders/Dockerfile.qpdf
build-platforms: linux/amd64
build-json: ${{ needs.prepare-docker-build.outputs.qpdf-json }}
build-args: |
QPDF_VERSION=${{ fromJSON(needs.prepare-docker-build.outputs.qpdf-json).version }}

View File

@ -13,6 +13,10 @@ on:
required: false
default: ""
type: string
build-platforms:
required: false
default: linux/amd64,linux/arm64,linux/arm/v7
type: string
concurrency:
group: ${{ github.workflow }}-${{ fromJSON(inputs.build-json).name }}-${{ fromJSON(inputs.build-json).version }}
@ -46,7 +50,7 @@ jobs:
context: .
file: ${{ inputs.dockerfile }}
tags: ${{ fromJSON(inputs.build-json).image_tag }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
platforms: ${{ inputs.build-platforms }}
build-args: ${{ inputs.build-args }}
push: true
cache-from: type=registry,ref=${{ fromJSON(inputs.build-json).cache_tag }}

View File

@ -58,6 +58,12 @@ LABEL org.opencontainers.image.url="https://github.com/paperless-ngx/paperless-n
LABEL org.opencontainers.image.licenses="GPL-3.0-only"
ARG DEBIAN_FRONTEND=noninteractive
# Buildx provided
ARG TARGETARCH
ARG TARGETVARIANT
# Workflow provided
ARG QPDF_VERSION
#
# Begin installation and configuration
@ -194,8 +200,8 @@ RUN --mount=type=bind,from=qpdf-builder,target=/qpdf \
--mount=type=bind,from=pikepdf-builder,target=/pikepdf \
set -eux \
&& echo "Installing qpdf" \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/libqpdf29_*.deb \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/qpdf_*.deb \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/libqpdf29_*.deb \
&& apt-get install --yes --no-install-recommends /qpdf/usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/qpdf_*.deb \
&& echo "Installing pikepdf and dependencies" \
&& python3 -m pip install --no-cache-dir /pikepdf/usr/src/wheels/*.whl \
&& python3 -m pip list \

View File

@ -10,9 +10,9 @@
# Example Usage:
# ./build-docker-image.sh Dockerfile -t paperless-ngx:my-awesome-feature
set -eux
set -eu
if ! command -v jq; then
if ! command -v jq &> /dev/null ; then
echo "jq required"
exit 1
elif [ ! -f "$1" ]; then
@ -20,28 +20,62 @@ elif [ ! -f "$1" ]; then
exit 1
fi
# Parse what we can from Pipfile.lock
pikepdf_version=$(jq ".default.pikepdf.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
psycopg2_version=$(jq ".default.psycopg2.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
# Read this from the other config file
qpdf_version=$(jq ".qpdf.version" .build-config.json | sed 's/"//g')
jbig2enc_version=$(jq ".jbig2enc.version" .build-config.json | sed 's/"//g')
# Get the branch name (used for caching)
branch_name=$(git rev-parse --abbrev-ref HEAD)
# https://docs.docker.com/develop/develop-images/build_enhancements/
# Required to use cache-from
export DOCKER_BUILDKIT=1
# Parse eithe Pipfile.lock or the .build-config.json
jbig2enc_version=$(jq ".jbig2enc.version" .build-config.json | sed 's/"//g')
qpdf_version=$(jq ".qpdf.version" .build-config.json | sed 's/"//g')
psycopg2_version=$(jq ".default.psycopg2.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
pikepdf_version=$(jq ".default.pikepdf.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
pillow_version=$(jq ".default.pillow.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
lxml_version=$(jq ".default.lxml.version" Pipfile.lock | sed 's/=//g' | sed 's/"//g')
docker build --file "$1" \
base_filename="$(basename -- "${1}")"
build_args_str=""
cache_from_str=""
case "${base_filename}" in
*.jbig2enc)
build_args_str="--build-arg JBIG2ENC_VERSION=${jbig2enc_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/jbig2enc:${jbig2enc_version}"
;;
*.psycopg2)
build_args_str="--build-arg PSYCOPG2_VERSION=${psycopg2_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/psycopg2:${psycopg2_version}"
;;
*.qpdf)
build_args_str="--build-arg QPDF_VERSION=${qpdf_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/qpdf:${qpdf_version}"
;;
*.pikepdf)
build_args_str="--build-arg QPDF_VERSION=${qpdf_version} --build-arg PIKEPDF_VERSION=${pikepdf_version} --build-arg PILLOW_VERSION=${pillow_version} --build-arg LXML_VERSION=${lxml_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/pikepdf:${pikepdf_version}"
;;
Dockerfile)
build_args_str="--build-arg QPDF_VERSION=${qpdf_version} --build-arg PIKEPDF_VERSION=${pikepdf_version} --build-arg PSYCOPG2_VERSION=${psycopg2_version} --build-arg JBIG2ENC_VERSION=${jbig2enc_version}"
cache_from_str="--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:${branch_name} --cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:dev"
;;
*)
echo "Unable to match ${base_filename}"
exit 1
;;
esac
read -r -a build_args_arr <<< "${build_args_str}"
read -r -a cache_from_arr <<< "${cache_from_str}"
set -eux
docker buildx build --file "${1}" \
--progress=plain \
--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:"${branch_name}" \
--cache-from ghcr.io/paperless-ngx/paperless-ngx/builder/cache/app:dev \
--build-arg JBIG2ENC_VERSION="${jbig2enc_version}" \
--build-arg QPDF_VERSION="${qpdf_version}" \
--build-arg PIKEPDF_VERSION="${pikepdf_version}" \
--build-arg PILLOW_VERSION="${pillow_version}" \
--build-arg LXML_VERSION="${lxml_version}" \
--build-arg PSYCOPG2_VERSION="${psycopg2_version}" "${@:2}" .
--output=type=docker \
"${cache_from_arr[@]}" \
"${build_args_arr[@]}" \
"${@:2}" .

View File

@ -16,7 +16,13 @@ FROM python:3.9-slim-bullseye as main
LABEL org.opencontainers.image.description="A intermediate image with pikepdf wheel built"
# Buildx provided
ARG TARGETARCH
ARG TARGETVARIANT
ARG DEBIAN_FRONTEND=noninteractive
# Workflow provided
ARG QPDF_VERSION
ARG PIKEPDF_VERSION
# These are not used, but will still bust the cache if one changes
# Otherwise, the main image will try to build thing (and fail)
@ -54,7 +60,7 @@ ARG BUILD_PACKAGES="\
WORKDIR /usr/src
COPY --from=qpdf-builder /usr/src/qpdf/*.deb ./
COPY --from=qpdf-builder /usr/src/qpdf/${QPDF_VERSION}/${TARGETARCH}${TARGETVARIANT}/*.deb ./
# As this is an base image for a multi-stage final image
# the added size of the install is basically irrelevant
@ -77,6 +83,8 @@ RUN set -eux \
&& python3 -m pip wheel \
# Build the package at the required version
pikepdf==${PIKEPDF_VERSION} \
# Look to piwheels for additional pre-built wheels
--extra-index-url https://www.piwheels.org/simple \
# Output the *.whl into this directory
--wheel-dir wheels \
# Do not use a binary packge for the package being built
@ -86,6 +94,8 @@ RUN set -eux \
# Don't cache build files
--no-cache-dir \
&& ls -ahl wheels \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./wheels/pkg-list.txt \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \

View File

@ -42,6 +42,8 @@ RUN set -eux \
# Don't cache build files
--no-cache-dir \
&& ls -ahl wheels/ \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ./wheels/pkg-list.txt \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \

View File

@ -1,48 +1,156 @@
# This Dockerfile compiles the jbig2enc library
# Inputs:
# - QPDF_VERSION - the version of qpdf to build a .deb.
# Must be present as a deb-src in bookworm
#
# Stage: pre-build
# Purpose:
# - Installs common packages
# - Sets common environment variables related to dpkg
# - Aquires the qpdf source from bookwork
# Useful Links:
# - https://qpdf.readthedocs.io/en/stable/installation.html#system-requirements
# - https://wiki.debian.org/Multiarch/HOWTO
# - https://wiki.debian.org/CrossCompiling
#
FROM debian:bullseye-slim as main
FROM debian:bullseye-slim as pre-build
LABEL org.opencontainers.image.description="A intermediate image with qpdf built"
ARG DEBIAN_FRONTEND=noninteractive
# This must match to pikepdf's minimum at least
ARG QPDF_VERSION
ARG BUILD_PACKAGES="\
build-essential \
debhelper \
ARG COMMON_BUILD_PACKAGES="\
cmake \
debhelper\
debian-keyring \
devscripts \
equivs \
libtool \
# https://qpdf.readthedocs.io/en/stable/installation.html#system-requirements
libjpeg62-turbo-dev \
libgnutls28-dev \
dpkg-dev \
equivs \
packaging-dev \
cmake \
zlib1g-dev"
libtool"
ENV DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2"
WORKDIR /usr/src
RUN set -eux \
&& echo "Installing build tools" \
&& echo "Installing common packages" \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends $BUILD_PACKAGES \
&& echo "Getting qpdf src" \
&& apt-get install --yes --quiet --no-install-recommends ${COMMON_BUILD_PACKAGES} \
&& echo "Getting qpdf source" \
&& echo "deb-src http://deb.debian.org/debian/ bookworm main" > /etc/apt/sources.list.d/bookworm-src.list \
&& apt-get update \
&& mkdir qpdf \
&& cd qpdf \
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm \
&& echo "Building qpdf" \
&& cd qpdf-$QPDF_VERSION \
&& export DEB_BUILD_OPTIONS="terse nocheck nodoc parallel=2" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
&& ls -ahl ../*.deb \
&& echo "Cleaning up image" \
&& apt-get -y purge ${BUILD_PACKAGES} \
&& apt-get -y autoremove --purge \
&& rm -rf /var/lib/apt/lists/*
&& apt-get update --quiet \
&& apt-get source --yes --quiet qpdf=${QPDF_VERSION}-1/bookworm
#
# Stage: amd64-builder
# Purpose: Builds qpdf for x86_64 (native build)
#
FROM pre-build as amd64-builder
ARG AMD64_BUILD_PACKAGES="\
build-essential \
libjpeg62-turbo-dev:amd64 \
libgnutls28-dev:amd64 \
zlib1g-dev:amd64"
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
RUN set -eux \
&& echo "Beginning amd64" \
&& echo "Install amd64 packages" \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends ${AMD64_BUILD_PACKAGES} \
&& echo "Building amd64" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean \
&& echo "Removing debug files" \
&& rm -f ../libqpdf29-dbgsym* \
&& rm -f ../qpdf-dbgsym* \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
#
# Stage: armhf-builder
# Purpose:
# - Sets armhf specific environment
# - Builds qpdf for armhf (cross compile)
#
FROM pre-build as armhf-builder
ARG ARMHF_PACKAGES="\
crossbuild-essential-armhf \
libjpeg62-turbo-dev:armhf \
libgnutls28-dev:armhf \
zlib1g-dev:armhf"
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
ENV CXX="/usr/bin/arm-linux-gnueabihf-g++" \
CC="/usr/bin/arm-linux-gnueabihf-gcc"
RUN set -eux \
&& echo "Beginning armhf" \
&& echo "Install armhf packages" \
&& dpkg --add-architecture armhf \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends ${ARMHF_PACKAGES} \
&& echo "Building armhf" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean --host-arch armhf \
&& echo "Removing debug files" \
&& rm -f ../libqpdf29-dbgsym* \
&& rm -f ../qpdf-dbgsym* \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
#
# Stage: aarch64-builder
# Purpose:
# - Sets aarch64 specific environment
# - Builds qpdf for aarch64 (cross compile)
#
FROM pre-build as aarch64-builder
ARG ARM64_PACKAGES="\
crossbuild-essential-arm64 \
libjpeg62-turbo-dev:arm64 \
libgnutls28-dev:arm64 \
zlib1g-dev:arm64"
ENV CXX="/usr/bin/aarch64-linux-gnu-g++" \
CC="/usr/bin/aarch64-linux-gnu-gcc"
WORKDIR /usr/src/qpdf-${QPDF_VERSION}
RUN set -eux \
&& echo "Beginning arm64" \
&& echo "Install arm64 packages" \
&& dpkg --add-architecture arm64 \
&& apt-get update --quiet \
&& apt-get install --yes --quiet --no-install-recommends ${ARM64_PACKAGES} \
&& echo "Building arm64" \
&& dpkg-buildpackage --build=binary --unsigned-source --unsigned-changes --post-clean --host-arch arm64 \
&& echo "Removing debug files" \
&& rm -f ../libqpdf29-dbgsym* \
&& rm -f ../qpdf-dbgsym* \
&& echo "Gathering package data" \
&& dpkg-query -f '${Package;-40}${Version}\n' -W > ../pkg-list.txt
#
# Stage: package
# Purpose: Holds the compiled .deb files in arch/variant specific folders
#
FROM alpine:3.17 as package
LABEL org.opencontainers.image.description="A image with qpdf installers stored in architecture & version specific folders"
ARG QPDF_VERSION
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/amd64
COPY --from=amd64-builder /usr/src/*.deb ./
COPY --from=amd64-builder /usr/src/pkg-list.txt ./
# Note this is ${TARGETARCH}${TARGETVARIANT} for armv7
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/armv7
COPY --from=armhf-builder /usr/src/*.deb ./
COPY --from=armhf-builder /usr/src/pkg-list.txt ./
WORKDIR /usr/src/qpdf/${QPDF_VERSION}/arm64
COPY --from=aarch64-builder /usr/src/*.deb ./
COPY --from=aarch64-builder /usr/src/pkg-list.txt ./

View File

@ -20,7 +20,6 @@ wait_for_postgres() {
exit 1
else
echo "Attempt $attempt_num failed! Trying again in 5 seconds..."
fi
attempt_num=$(("$attempt_num" + 1))
@ -67,10 +66,16 @@ migrations() {
# of the current container starts.
flock 200
echo "Apply database migrations..."
python3 manage.py migrate
python3 manage.py migrate --skip-checks --no-input
) 200>"${DATA_DIR}/migration_lock"
}
django_checks() {
# Explicitly run the Django system checks
echo "Running Django checks"
python3 manage.py check
}
search_index() {
local -r index_version=1
@ -100,6 +105,8 @@ do_work() {
migrations
django_checks
search_index
superuser

View File

@ -233,6 +233,7 @@ optional arguments:
-c, --compare-checksums
-f, --use-filename-format
-d, --delete
-z --zip
```
`target` is a folder to which the data gets written. This includes
@ -258,6 +259,9 @@ current export such as files from deleted documents, specify `--delete`.
Be careful when pointing paperless to a directory that already contains
other files.
If `-z` or `--zip` is provided, the export will be a zipfile
in the target directory, named according to the current date.
The filenames generated by this command follow the format
`[date created] [correspondent] [title].[extension]`. If you want
paperless to use `PAPERLESS_FILENAME_FORMAT` for exported filenames

51
src-ui/.eslintrc.json Normal file
View File

@ -0,0 +1,51 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

View File

@ -1,179 +1,196 @@
{
"$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",
"i18n": {
"sourceLocale": "en-US",
"locales": {
"be-BY": "src/locale/messages.be_BY.xlf",
"cs-CZ": "src/locale/messages.cs_CZ.xlf",
"da-DK": "src/locale/messages.da_DK.xlf",
"de-DE": "src/locale/messages.de_DE.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"es-ES": "src/locale/messages.es_ES.xlf",
"fr-FR": "src/locale/messages.fr_FR.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"pl-PL": "src/locale/messages.pl_PL.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"pt-PT": "src/locale/messages.pt_PT.xlf",
"ro-RO": "src/locale/messages.ro_RO.xlf",
"ru-RU": "src/locale/messages.ru_RU.xlf",
"sl-SI": "src/locale/messages.sl_SI.xlf",
"sr-CS": "src/locale/messages.sr_CS.xlf",
"sv-SE": "src/locale/messages.sv_SE.xlf",
"tr-TR": "src/locale/messages.tr_TR.xlf",
"zh-CN": "src/locale/messages.zh_CN.xlf"
}
},
"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",
"localize": true,
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest", {
"glob": "pdf.worker.min.js",
"input": "node_modules/pdfjs-dist/build/",
"output": "/assets/js/"
}
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"allowedCommonJsDependencies": [
"ng2-pdf-viewer"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "../src/documents/static/frontend/",
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
},
"en-US": {
"localize": ["en-US"]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build:en-US"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
}
}
}
},
"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",
"i18n": {
"sourceLocale": "en-US",
"locales": {
"be-BY": "src/locale/messages.be_BY.xlf",
"cs-CZ": "src/locale/messages.cs_CZ.xlf",
"da-DK": "src/locale/messages.da_DK.xlf",
"de-DE": "src/locale/messages.de_DE.xlf",
"en-GB": "src/locale/messages.en_GB.xlf",
"es-ES": "src/locale/messages.es_ES.xlf",
"fr-FR": "src/locale/messages.fr_FR.xlf",
"it-IT": "src/locale/messages.it_IT.xlf",
"lb-LU": "src/locale/messages.lb_LU.xlf",
"nl-NL": "src/locale/messages.nl_NL.xlf",
"pl-PL": "src/locale/messages.pl_PL.xlf",
"pt-BR": "src/locale/messages.pt_BR.xlf",
"pt-PT": "src/locale/messages.pt_PT.xlf",
"ro-RO": "src/locale/messages.ro_RO.xlf",
"ru-RU": "src/locale/messages.ru_RU.xlf",
"sl-SI": "src/locale/messages.sl_SI.xlf",
"sr-CS": "src/locale/messages.sr_CS.xlf",
"sv-SE": "src/locale/messages.sv_SE.xlf",
"tr-TR": "src/locale/messages.tr_TR.xlf",
"zh-CN": "src/locale/messages.zh_CN.xlf"
}
},
"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",
"localize": true,
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest",
{
"glob": "pdf.worker.min.js",
"input": "node_modules/pdfjs-dist/build/",
"output": "/assets/js/"
}
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"allowedCommonJsDependencies": [
"ng2-pdf-viewer"
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputPath": "../src/documents/static/frontend/",
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
},
"en-US": {
"localize": [
"en-US"
]
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "paperless-ui:build:en-US"
},
"configurations": {
"production": {
"browserTarget": "paperless-ui:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "paperless-ui:build"
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/apple-touch-icon.png",
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve",
"watch": true,
"headless": false
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-run": {
"builder": "@cypress/schematic:cypress",
"options": {
"devServerTarget": "paperless-ui:serve"
},
"configurations": {
"production": {
"devServerTarget": "paperless-ui:serve:production"
}
}
},
"cypress-open": {
"builder": "@cypress/schematic:cypress",
"options": {
"watch": true,
"headless": false
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
},
"defaultProject": "paperless-ui",
"cli": {
"schematicCollections": [
"@angular-eslint/schematics"
]
}
}

View File

@ -711,7 +711,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">476</context>
<context context-type="linenumber">492</context>
</context-group>
</trans-unit>
<trans-unit id="2526035785704676448" datatype="html">
@ -758,19 +758,19 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">234</context>
<context context-type="linenumber">279</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">272</context>
<context context-type="linenumber">319</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">308</context>
<context context-type="linenumber">357</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">344</context>
<context context-type="linenumber">395</context>
</context-group>
</trans-unit>
<trans-unit id="6371576811194810854" datatype="html">
@ -967,7 +967,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">35</context>
<context context-type="linenumber">36</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
@ -1006,7 +1006,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">36</context>
<context context-type="linenumber">37</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component.html</context>
@ -1120,39 +1120,39 @@
<context context-type="linenumber">18</context>
</context-group>
</trans-unit>
<trans-unit id="8758081884575368561" datatype="html">
<source>Create new mail account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">22</context>
</context-group>
</trans-unit>
<trans-unit id="5559445021532852612" datatype="html">
<source>Edit mail account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">26</context>
</context-group>
</trans-unit>
<trans-unit id="451418349275958054" datatype="html">
<source>No encryption</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">43</context>
<context context-type="linenumber">12</context>
</context-group>
</trans-unit>
<trans-unit id="3719080555538542367" datatype="html">
<source>SSL</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">44</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit>
<trans-unit id="2620794666957669114" datatype="html">
<source>STARTTLS</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">45</context>
<context context-type="linenumber">14</context>
</context-group>
</trans-unit>
<trans-unit id="8758081884575368561" datatype="html">
<source>Create new mail account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
<trans-unit id="5559445021532852612" datatype="html">
<source>Edit mail account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-account-edit-dialog/mail-account-edit-dialog.component.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="4086606389696938932" datatype="html">
@ -1285,39 +1285,36 @@
<context context-type="linenumber">30</context>
</context-group>
</trans-unit>
<trans-unit id="3147349817770432927" datatype="html">
<source>Create new mail rule</source>
<trans-unit id="1519954996184640001" datatype="html">
<source>Error</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">57</context>
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.html</context>
<context context-type="linenumber">35</context>
</context-group>
</trans-unit>
<trans-unit id="3374331029704382439" datatype="html">
<source>Edit mail rule</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">61</context>
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="6233529027580744166" datatype="html">
<source>Only process attachments.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">98</context>
<context context-type="linenumber">24</context>
</context-group>
</trans-unit>
<trans-unit id="3860563069570088911" datatype="html">
<source>Process all files, including &apos;inline&apos; attachments.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">102</context>
<context context-type="linenumber">28</context>
</context-group>
</trans-unit>
<trans-unit id="7022070615528435141" datatype="html">
<source>Delete</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">111</context>
<context context-type="linenumber">35</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
@ -1325,7 +1322,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">97</context>
<context context-type="linenumber">126</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
@ -1380,70 +1377,84 @@
<source>Move to specified folder</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">115</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="4593278936733161020" datatype="html">
<source>Mark as read, don&apos;t process read mails</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">119</context>
<context context-type="linenumber">43</context>
</context-group>
</trans-unit>
<trans-unit id="2378921144019636516" datatype="html">
<source>Flag the mail, don&apos;t process flagged mails</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">123</context>
<context context-type="linenumber">47</context>
</context-group>
</trans-unit>
<trans-unit id="6457024618858980302" datatype="html">
<source>Tag the mail with specified tag, don&apos;t process tagged mails</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">127</context>
<context context-type="linenumber">51</context>
</context-group>
</trans-unit>
<trans-unit id="4673329664686432878" datatype="html">
<source>Use subject as title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">136</context>
<context context-type="linenumber">58</context>
</context-group>
</trans-unit>
<trans-unit id="8645471396972938185" datatype="html">
<source>Use attachment filename as title</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">140</context>
<context context-type="linenumber">62</context>
</context-group>
</trans-unit>
<trans-unit id="1568902914205618549" datatype="html">
<source>Do not assign a correspondent</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">149</context>
<context context-type="linenumber">69</context>
</context-group>
</trans-unit>
<trans-unit id="3567746385454588269" datatype="html">
<source>Use mail address</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">153</context>
<context context-type="linenumber">73</context>
</context-group>
</trans-unit>
<trans-unit id="445154175758965852" datatype="html">
<source>Use name (or mail address if not available)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">157</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="1258862217749148424" datatype="html">
<source>Use correspondent selected below</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">161</context>
<context context-type="linenumber">81</context>
</context-group>
</trans-unit>
<trans-unit id="3147349817770432927" datatype="html">
<source>Create new mail rule</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">121</context>
</context-group>
</trans-unit>
<trans-unit id="3374331029704382439" datatype="html">
<source>Edit mail rule</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/common/edit-dialog/mail-rule-edit-dialog/mail-rule-edit-dialog.component.ts</context>
<context context-type="linenumber">125</context>
</context-group>
</trans-unit>
<trans-unit id="6036319582202941456" datatype="html">
@ -1801,32 +1812,32 @@
<source>Processing: <x id="PH" equiv-text="countUploadingAndProcessing"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">37</context>
<context context-type="linenumber">36</context>
</context-group>
</trans-unit>
<trans-unit id="9182918211699394982" datatype="html">
<source>Failed: <x id="PH" equiv-text="countFailed"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">40</context>
<context context-type="linenumber">39</context>
</context-group>
</trans-unit>
<trans-unit id="534116346205124059" datatype="html">
<source>Added: <x id="PH" equiv-text="countSuccess"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">43</context>
<context context-type="linenumber">42</context>
</context-group>
</trans-unit>
<trans-unit id="760986369763309193" datatype="html">
<source>, </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
<context context-type="linenumber">46</context>
<context context-type="linenumber">45</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">179</context>
<context context-type="linenumber">224</context>
</context-group>
<note priority="1" from="description">this string is used to separate processing, failed and added on the file upload widget</note>
</trans-unit>
@ -1931,6 +1942,10 @@
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
<context context-type="linenumber">19</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">90</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
<context context-type="linenumber">58</context>
@ -1955,7 +1970,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">90</context>
<context context-type="linenumber">77</context>
</context-group>
</trans-unit>
<trans-unit id="1418444397960583910" datatype="html">
@ -2258,7 +2273,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">387</context>
<context context-type="linenumber">454</context>
</context-group>
</trans-unit>
<trans-unit id="9197453786953646058" datatype="html">
@ -2276,19 +2291,19 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">364</context>
<context context-type="linenumber">417</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">389</context>
<context context-type="linenumber">456</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">560</context>
<context context-type="linenumber">576</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">619</context>
<context context-type="linenumber">635</context>
</context-group>
</trans-unit>
<trans-unit id="1181910457994920507" datatype="html">
@ -2299,15 +2314,15 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">391</context>
<context context-type="linenumber">458</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">562</context>
<context context-type="linenumber">578</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">621</context>
<context context-type="linenumber">637</context>
</context-group>
</trans-unit>
<trans-unit id="5729001209753056399" datatype="html">
@ -2388,7 +2403,7 @@
<source>Actions</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">75</context>
<context context-type="linenumber">74</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
@ -2423,18 +2438,32 @@
<context context-type="linenumber">44</context>
</context-group>
</trans-unit>
<trans-unit id="1559181160090274453" datatype="html">
<source> Download <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div *ngIf=&quot;awaitingDownload&quot; class=&quot;spinner-border spinner-border-sm&quot; role=&quot;status&quot;&gt;"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;visually-hidden&quot;&gt;"/>Preparing download...<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/></source>
<trans-unit id="1015374532025907183" datatype="html">
<source>Include:</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">78,82</context>
<context context-type="linenumber">96</context>
</context-group>
</trans-unit>
<trans-unit id="9202029122138685465" datatype="html">
<source> Download originals <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div *ngIf=&quot;awaitingDownload&quot; class=&quot;spinner-border spinner-border-sm&quot; role=&quot;status&quot;&gt;"/><x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span class=&quot;visually-hidden&quot;&gt;"/>Preparing download...<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/></source>
<trans-unit id="1208547554603365604" datatype="html">
<source> Archived files </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">84,88</context>
<context context-type="linenumber">100,102</context>
</context-group>
</trans-unit>
<trans-unit id="6791570188945688785" datatype="html">
<source> Original files </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">106,108</context>
</context-group>
</trans-unit>
<trans-unit id="3608345051493493574" datatype="html">
<source> Use formatted filename </source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
<context context-type="linenumber">113,115</context>
</context-group>
</trans-unit>
<trans-unit id="7985804062689412812" datatype="html">
@ -2443,25 +2472,25 @@
)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">103,105</context>
<context context-type="linenumber">144,146</context>
</context-group>
</trans-unit>
<trans-unit id="7894972847287473517" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">171</context>
<context context-type="linenumber">216</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">177</context>
<context context-type="linenumber">222</context>
</context-group>
</trans-unit>
<trans-unit id="8639884465898458690" datatype="html">
<source>&quot;<x id="PH" equiv-text="items[0].name"/>&quot; and &quot;<x id="PH_1" equiv-text="items[1].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">173</context>
<context context-type="linenumber">218</context>
</context-group>
<note priority="1" from="description">This is for messages like &apos;modify &quot;tag1&quot; and &quot;tag2&quot;&apos;</note>
</trans-unit>
@ -2469,7 +2498,7 @@
<source><x id="PH" equiv-text="list"/> and &quot;<x id="PH_1" equiv-text="items[items.length - 1].name"/>&quot;</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">181,183</context>
<context context-type="linenumber">226,228</context>
</context-group>
<note priority="1" from="description">this is for messages like &apos;modify &quot;tag1&quot;, &quot;tag2&quot; and &quot;tag3&quot;&apos;</note>
</trans-unit>
@ -2477,14 +2506,14 @@
<source>Confirm tags assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">198</context>
<context context-type="linenumber">243</context>
</context-group>
</trans-unit>
<trans-unit id="6619516195038467207" datatype="html">
<source>This operation will add the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">204</context>
<context context-type="linenumber">249</context>
</context-group>
</trans-unit>
<trans-unit id="1894412783609570695" datatype="html">
@ -2493,14 +2522,14 @@
)"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">209,211</context>
<context context-type="linenumber">254,256</context>
</context-group>
</trans-unit>
<trans-unit id="7181166515756808573" datatype="html">
<source>This operation will remove the tag &quot;<x id="PH" equiv-text="tag.name"/>&quot; from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">217</context>
<context context-type="linenumber">262</context>
</context-group>
</trans-unit>
<trans-unit id="3819792277998068944" datatype="html">
@ -2509,7 +2538,7 @@
)"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">222,224</context>
<context context-type="linenumber">267,269</context>
</context-group>
</trans-unit>
<trans-unit id="2739066218579571288" datatype="html">
@ -2520,98 +2549,98 @@
)"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">226,230</context>
<context context-type="linenumber">271,275</context>
</context-group>
</trans-unit>
<trans-unit id="2996713129519325161" datatype="html">
<source>Confirm correspondent assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">265</context>
<context context-type="linenumber">312</context>
</context-group>
</trans-unit>
<trans-unit id="6900893559485781849" datatype="html">
<source>This operation will assign the correspondent &quot;<x id="PH" equiv-text="correspondent.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">267</context>
<context context-type="linenumber">314</context>
</context-group>
</trans-unit>
<trans-unit id="1257522660364398440" datatype="html">
<source>This operation will remove the correspondent from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">269</context>
<context context-type="linenumber">316</context>
</context-group>
</trans-unit>
<trans-unit id="5393409374423140648" datatype="html">
<source>Confirm document type assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">301</context>
<context context-type="linenumber">350</context>
</context-group>
</trans-unit>
<trans-unit id="332180123895325027" datatype="html">
<source>This operation will assign the document type &quot;<x id="PH" equiv-text="documentType.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">303</context>
<context context-type="linenumber">352</context>
</context-group>
</trans-unit>
<trans-unit id="2236642492594872779" datatype="html">
<source>This operation will remove the document type from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">305</context>
<context context-type="linenumber">354</context>
</context-group>
</trans-unit>
<trans-unit id="6386555513013840736" datatype="html">
<source>Confirm storage path assignment</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">337</context>
<context context-type="linenumber">388</context>
</context-group>
</trans-unit>
<trans-unit id="8750527458618415924" datatype="html">
<source>This operation will assign the storage path &quot;<x id="PH" equiv-text="storagePath.name"/>&quot; to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">339</context>
<context context-type="linenumber">390</context>
</context-group>
</trans-unit>
<trans-unit id="60728365335056946" datatype="html">
<source>This operation will remove the storage path from <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">341</context>
<context context-type="linenumber">392</context>
</context-group>
</trans-unit>
<trans-unit id="749430623564850405" datatype="html">
<source>Delete confirm</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">362</context>
<context context-type="linenumber">415</context>
</context-group>
</trans-unit>
<trans-unit id="4303174930844518780" datatype="html">
<source>This operation will permanently delete <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">363</context>
<context context-type="linenumber">416</context>
</context-group>
</trans-unit>
<trans-unit id="6734339521247847366" datatype="html">
<source>Delete document(s)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">366</context>
<context context-type="linenumber">419</context>
</context-group>
</trans-unit>
<trans-unit id="8968869182645922415" datatype="html">
<source>This operation will permanently redo OCR for <x id="PH" equiv-text="this.list.selected.size"/> selected document(s).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
<context context-type="linenumber">388</context>
<context context-type="linenumber">455</context>
</context-group>
</trans-unit>
<trans-unit id="8076495233090006322" datatype="html">
@ -3653,49 +3682,49 @@
<source>Saved view &quot;<x id="PH" equiv-text="savedView.name"/>&quot; deleted.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">367</context>
<context context-type="linenumber">383</context>
</context-group>
</trans-unit>
<trans-unit id="3891152409365583719" datatype="html">
<source>Settings saved</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">460</context>
<context context-type="linenumber">476</context>
</context-group>
</trans-unit>
<trans-unit id="7217000812750597833" datatype="html">
<source>Settings were saved successfully.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">461</context>
<context context-type="linenumber">477</context>
</context-group>
</trans-unit>
<trans-unit id="525012668859298131" datatype="html">
<source>Settings were saved successfully. Reload is required to apply some changes.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">465</context>
<context context-type="linenumber">481</context>
</context-group>
</trans-unit>
<trans-unit id="8491974984518503778" datatype="html">
<source>Reload now</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">466</context>
<context context-type="linenumber">482</context>
</context-group>
</trans-unit>
<trans-unit id="6839066544204061364" datatype="html">
<source>Use system language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">484</context>
<context context-type="linenumber">500</context>
</context-group>
</trans-unit>
<trans-unit id="7729897675462249787" datatype="html">
<source>Use date format of display language</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">491</context>
<context context-type="linenumber">507</context>
</context-group>
</trans-unit>
<trans-unit id="8488620293789898901" datatype="html">
@ -3704,91 +3733,91 @@
)"/></source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">511,513</context>
<context context-type="linenumber">527,529</context>
</context-group>
</trans-unit>
<trans-unit id="6327501535846658797" datatype="html">
<source>Saved account &quot;<x id="PH" equiv-text="newMailAccount.name"/>&quot;.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">538</context>
<context context-type="linenumber">554</context>
</context-group>
</trans-unit>
<trans-unit id="6428427497555765743" datatype="html">
<source>Error saving account: <x id="PH" equiv-text="e.toString()"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">548</context>
<context context-type="linenumber">564</context>
</context-group>
</trans-unit>
<trans-unit id="5641934153807844674" datatype="html">
<source>Confirm delete mail account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">558</context>
<context context-type="linenumber">574</context>
</context-group>
</trans-unit>
<trans-unit id="2105352414355663637" datatype="html">
<source>This operation will permanently this mail account.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">559</context>
<context context-type="linenumber">575</context>
</context-group>
</trans-unit>
<trans-unit id="4233826387148482123" datatype="html">
<source>Deleted mail account</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">568</context>
<context context-type="linenumber">584</context>
</context-group>
</trans-unit>
<trans-unit id="7443801450153832973" datatype="html">
<source>Error deleting mail account: <x id="PH" equiv-text="e.toString()"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">577</context>
<context context-type="linenumber">593</context>
</context-group>
</trans-unit>
<trans-unit id="123368655395433699" datatype="html">
<source>Saved rule &quot;<x id="PH" equiv-text="newMailRule.name"/>&quot;.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">596</context>
<context context-type="linenumber">612</context>
</context-group>
</trans-unit>
<trans-unit id="4741216051394823471" datatype="html">
<source>Error saving rule: <x id="PH" equiv-text="e.toString()"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">607</context>
<context context-type="linenumber">623</context>
</context-group>
</trans-unit>
<trans-unit id="3896080636020672118" datatype="html">
<source>Confirm delete mail rule</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">617</context>
<context context-type="linenumber">633</context>
</context-group>
</trans-unit>
<trans-unit id="6183247517597275616" datatype="html">
<source>This operation will permanently this mail rule.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">618</context>
<context context-type="linenumber">634</context>
</context-group>
</trans-unit>
<trans-unit id="9077981247971516916" datatype="html">
<source>Deleted mail rule</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">627</context>
<context context-type="linenumber">643</context>
</context-group>
</trans-unit>
<trans-unit id="4740074357089345173" datatype="html">
<source>Error deleting mail rule: <x id="PH" equiv-text="e.toString()"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/manage/settings/settings.component.ts</context>
<context context-type="linenumber">636</context>
<context context-type="linenumber">652</context>
</context-group>
</trans-unit>
<trans-unit id="5101757640976222639" datatype="html">
@ -4041,7 +4070,7 @@
<source>Unsaved Changes</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/guards/dirty-form.guard.ts</context>
<context context-type="linenumber">18</context>
<context context-type="linenumber">17</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context>
@ -4049,36 +4078,36 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">116</context>
<context context-type="linenumber">103</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">143</context>
<context context-type="linenumber">130</context>
</context-group>
</trans-unit>
<trans-unit id="2573823578527613511" datatype="html">
<source>You have unsaved changes.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/guards/dirty-form.guard.ts</context>
<context context-type="linenumber">19</context>
<context context-type="linenumber">18</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">144</context>
<context context-type="linenumber">131</context>
</context-group>
</trans-unit>
<trans-unit id="3305084982600522070" datatype="html">
<source>Are you sure you want to leave?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/guards/dirty-form.guard.ts</context>
<context context-type="linenumber">20</context>
<context context-type="linenumber">19</context>
</context-group>
</trans-unit>
<trans-unit id="729881853265307704" datatype="html">
<source>Leave page</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/guards/dirty-form.guard.ts</context>
<context context-type="linenumber">22</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="1649285023712919370" datatype="html">
@ -4222,35 +4251,35 @@
<source>You have unsaved changes to the document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">118</context>
<context context-type="linenumber">105</context>
</context-group>
</trans-unit>
<trans-unit id="2089045849587358256" datatype="html">
<source>Are you sure you want to close this document?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">122</context>
<context context-type="linenumber">109</context>
</context-group>
</trans-unit>
<trans-unit id="2885986061416655600" datatype="html">
<source>Close document</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">124</context>
<context context-type="linenumber">111</context>
</context-group>
</trans-unit>
<trans-unit id="6755718693176327396" datatype="html">
<source>Are you sure you want to close all documents?</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">145</context>
<context context-type="linenumber">132</context>
</context-group>
</trans-unit>
<trans-unit id="4215561719980781894" datatype="html">
<source>Close documents</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/open-documents.service.ts</context>
<context context-type="linenumber">147</context>
<context context-type="linenumber">134</context>
</context-group>
</trans-unit>
<trans-unit id="3553216189604488439" datatype="html">
@ -4436,13 +4465,6 @@
<context context-type="linenumber">394</context>
</context-group>
</trans-unit>
<trans-unit id="1519954996184640001" datatype="html">
<source>Error</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/services/toast.service.ts</context>
<context context-type="linenumber">32</context>
</context-group>
</trans-unit>
<trans-unit id="5037437391296624618" datatype="html">
<source>Information</source>
<context-group purpose="location">

3239
src-ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,17 +40,23 @@
"devDependencies": {
"@angular-builders/jest": "14.1.0",
"@angular-devkit/build-angular": "~14.2.7",
"@angular-eslint/builder": "14.4.0",
"@angular-eslint/eslint-plugin": "14.4.0",
"@angular-eslint/eslint-plugin-template": "14.4.0",
"@angular-eslint/schematics": "14.4.0",
"@angular-eslint/template-parser": "14.4.0",
"@angular/cli": "~14.2.7",
"@angular/compiler-cli": "~14.2.8",
"@types/jest": "28.1.6",
"@types/node": "^18.7.23",
"codelyzer": "^6.0.2",
"@typescript-eslint/eslint-plugin": "5.43.0",
"@typescript-eslint/parser": "5.43.0",
"concurrently": "7.4.0",
"eslint": "^8.28.0",
"jest": "28.1.3",
"jest-environment-jsdom": "^29.2.2",
"jest-preset-angular": "^12.2.3",
"ts-node": "~10.9.1",
"tslint": "~6.1.3",
"typescript": "~4.8.4",
"wait-on": "~6.0.1"
},

View File

@ -24,7 +24,7 @@ import { CorrespondentEditDialogComponent } from './components/common/edit-dialo
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
import { TagComponent } from './components/common/tag/tag.component'
import { ClearableBadge } from './components/common/clearable-badge/clearable-badge.component'
import { ClearableBadgeComponent } from './components/common/clearable-badge/clearable-badge.component'
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
import { AppFrameComponent } from './components/app-frame/app-frame.component'
import { ToastsComponent } from './components/common/toasts/toasts.component'
@ -157,7 +157,7 @@ function initializeApp(settings: SettingsService) {
DocumentTypeEditDialogComponent,
StoragePathEditDialogComponent,
TagComponent,
ClearableBadge,
ClearableBadgeComponent,
PageHeaderComponent,
AppFrameComponent,
ToastsComponent,

View File

@ -220,6 +220,12 @@ main {
font-size: 1rem;
}
@media screen and (min-width: 768px) {
.navbar-brand.slim {
max-width: 50px;
}
}
.dropdown.show .dropdown-toggle,
.dropdown-toggle:hover {
opacity: 0.7;

View File

@ -5,7 +5,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'
templateUrl: './clearable-badge.component.html',
styleUrls: ['./clearable-badge.component.scss'],
})
export class ClearableBadge {
export class ClearableBadgeComponent {
constructor() {}
@Input()

View File

@ -30,7 +30,7 @@ export abstract class EditDialogComponent<
object: T
@Output()
success = new EventEmitter()
succeeded = new EventEmitter()
networkActive = false
@ -119,7 +119,7 @@ export abstract class EditDialogComponent<
serverResponse.subscribe({
next: (result) => {
this.activeModal.close()
this.success.emit(result)
this.succeeded.emit(result)
},
error: (error) => {
this.error = error.error

View File

@ -6,7 +6,7 @@
</div>
<div class="modal-body">
<p *ngIf="this.dialogMode == 'edit'" i18n>
<p *ngIf="this.dialogMode === 'edit'" i18n>
<em>Note that editing a path does not apply changes to stored files until you have run the 'document_renamer' utility. See the <a target="_blank" href="https://docs.paperless-ngx.com/administration/#renamer">documentation</a>.</em>
</p>

View File

@ -324,7 +324,7 @@ export class FilterableDropdownComponent {
apply = new EventEmitter<ChangedItems>()
@Output()
open = new EventEmitter()
opened = new EventEmitter()
get operatorToggleEnabled(): boolean {
return (
@ -359,7 +359,7 @@ export class FilterableDropdownComponent {
if (this.editing) {
this.selectionModel.reset()
}
this.open.next(this)
this.opened.next(this)
} else {
this.filterText = ''
if (this.applyOnClose && this.selectionModel.isDirty()) {

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { Component, EventEmitter, Input, Output } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { ObjectWithId } from 'src/app/data/object-with-id'
@ -7,7 +7,7 @@ import { ObjectWithId } from 'src/app/data/object-with-id'
templateUrl: './select-dialog.component.html',
styleUrls: ['./select-dialog.component.scss'],
})
export class SelectDialogComponent implements OnInit {
export class SelectDialogComponent {
constructor(public activeModal: NgbActiveModal) {}
@Output()
@ -24,8 +24,6 @@ export class SelectDialogComponent implements OnInit {
selected: number
ngOnInit(): void {}
cancelClicked() {
this.activeModal.close()
}

View File

@ -1,4 +1,4 @@
import { Component, Input, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
import { PaperlessTag } from 'src/app/data/paperless-tag'
@Component({
@ -6,7 +6,7 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
templateUrl: './tag.component.html',
styleUrls: ['./tag.component.scss'],
})
export class TagComponent implements OnInit {
export class TagComponent {
constructor() {}
@Input()
@ -17,6 +17,4 @@ export class TagComponent implements OnInit {
@Input()
clickable: boolean = false
ngOnInit(): void {}
}

View File

@ -1,6 +1,6 @@
<app-widget-frame title="Statistics" [loading]="loading" i18n-title>
<ng-container content>
<p class="card-text" i18n *ngIf="statistics?.documents_inbox != null">Documents in inbox: {{statistics?.documents_inbox}}</p>
<p class="card-text" i18n *ngIf="statistics?.documents_inbox !== null">Documents in inbox: {{statistics?.documents_inbox}}</p>
<p class="card-text" i18n>Total documents: {{statistics?.documents_total}}</p>
</ng-container>
</app-widget-frame>

View File

@ -1,6 +1,5 @@
import { HttpEventType } from '@angular/common/http'
import { Component, OnInit } from '@angular/core'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import { Component } from '@angular/core'
import { NgxFileDropEntry } from 'ngx-file-drop'
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import {
ConsumerStatusService,
@ -16,10 +15,7 @@ const MAX_ALERTS = 5
templateUrl: './upload-file-widget.component.html',
styleUrls: ['./upload-file-widget.component.scss'],
})
export class UploadFileWidgetComponent
extends ComponentWithPermissions
implements OnInit
{
export class UploadFileWidgetComponent extends ComponentWithPermissions {
alertsExpanded = false
constructor(
@ -115,8 +111,6 @@ export class UploadFileWidgetComponent
this.consumerStatusService.dismissCompleted()
}
ngOnInit(): void {}
public fileOver(event) {}
public fileLeave(event) {}

View File

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'
import { Component } from '@angular/core'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
@Component({
@ -6,8 +6,6 @@ import { TourService } from 'ngx-ui-tour-ng-bootstrap'
templateUrl: './welcome-widget.component.html',
styleUrls: ['./welcome-widget.component.scss'],
})
export class WelcomeWidgetComponent implements OnInit {
export class WelcomeWidgetComponent {
constructor(public readonly tourService: TourService) {}
ngOnInit(): void {}
}

View File

@ -1,11 +1,11 @@
import { Component, Input, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-widget-frame',
templateUrl: './widget-frame.component.html',
styleUrls: ['./widget-frame.component.scss'],
})
export class WidgetFrameComponent implements OnInit {
export class WidgetFrameComponent {
constructor() {}
@Input()
@ -13,6 +13,4 @@ export class WidgetFrameComponent implements OnInit {
@Input()
loading: boolean = false
ngOnInit(): void {}
}

View File

@ -1,5 +1,5 @@
<app-page-header [(title)]="title">
<div class="input-group input-group-sm me-5 d-none d-md-flex" *ngIf="getContentType() == 'application/pdf' && !useNativePdfViewer">
<div class="input-group input-group-sm me-5 d-none d-md-flex" *ngIf="getContentType() === 'application/pdf' && !useNativePdfViewer">
<div class="input-group-text" i18n>Page</div>
<input class="form-control flex-grow-0 w-auto" type="number" min="1" [max]="previewNumPages" [(ngModel)]="previewCurrentPage" />
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
@ -149,9 +149,9 @@
<li [ngbNavItem]="4" class="d-md-none">
<a ngbNavLink>Preview</a>
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent == undefined">
<ng-template ngbNavContent *ngIf="pdfPreview.offsetParent === undefined">
<div class="position-relative">
<ng-container *ngIf="getContentType() == 'application/pdf'">
<ng-container *ngIf="getContentType() === 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
@ -159,7 +159,7 @@
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<ng-container *ngIf="getContentType() === 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">
@ -191,15 +191,15 @@
<div [ngbNavOutlet]="nav" class="mt-2"></div>
<ng-container>
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || !(isDirty$ | async)">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || !(isDirty$ | async) || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || !(isDirty$ | async) || error">Save</button>&nbsp;
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) === false">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) === false || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="!userCanEdit || networkActive || (isDirty$ | async) === false || error">Save</button>&nbsp;
</ng-container>
</form>
</div>
<div class="col-md-6 col-xl-8 mb-3 d-none d-md-block position-relative" #pdfPreview>
<ng-container *ngIf="getContentType() == 'application/pdf'">
<ng-container *ngIf="getContentType() === 'application/pdf'">
<div class="preview-sticky pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer">
<pdf-viewer [src]="{ url: previewUrl, password: password }" [original-size]="false" [show-borders]="true" [show-all]="true" [(page)]="previewCurrentPage" [render-text-mode]="2" (error)="onError($event)" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer>
</div>
@ -207,7 +207,7 @@
<object [data]="previewUrl | safeUrl" class="preview-sticky" width="100%"></object>
</ng-template>
</ng-container>
<ng-container *ngIf="getContentType() == 'text/plain'">
<ng-container *ngIf="getContentType() === 'text/plain'">
<object [data]="previewUrl | safeUrl" type="text/plain" class="preview-sticky bg-white" width="100%"></object>
</ng-container>
<div *ngIf="requiresPassword" class="password-prompt">

View File

@ -1,11 +1,11 @@
import { Component, Input, OnInit } from '@angular/core'
import { Component, Input } from '@angular/core'
@Component({
selector: 'app-metadata-collapse',
templateUrl: './metadata-collapse.component.html',
styleUrls: ['./metadata-collapse.component.scss'],
})
export class MetadataCollapseComponent implements OnInit {
export class MetadataCollapseComponent {
constructor() {}
expand = false
@ -15,6 +15,4 @@ export class MetadataCollapseComponent implements OnInit {
@Input()
title = $localize`Metadata`
ngOnInit(): void {}
}

View File

@ -2,7 +2,6 @@ import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core'
@ -21,10 +20,7 @@ import { ComponentWithPermissions } from '../../with-permissions/with-permission
'../popover-preview/popover-preview.scss',
],
})
export class DocumentCardLargeComponent
extends ComponentWithPermissions
implements OnInit
{
export class DocumentCardLargeComponent extends ComponentWithPermissions {
constructor(
private documentService: DocumentService,
private settingsService: SettingsService
@ -77,8 +73,6 @@ export class DocumentCardLargeComponent
}
}
ngOnInit(): void {}
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}

View File

@ -53,7 +53,7 @@
</div>
<div>
<button *ngFor="let f of getSortFields()" ngbDropdownItem (click)="setSortField(f.field)"
[class.active]="list.sortField == f.field">{{f.name}}
[class.active]="list.sortField === f.field">{{f.name}}
</button>
</div>
</div>
@ -96,7 +96,7 @@
</ng-container>
<span i18n *ngIf="list.selected.size > 0">{list.collectionSize, plural, =1 {Selected {{list.selected.size}} of one document} other {Selected {{list.selected.size}} of {{list.collectionSize || 0}} documents}}</span>
<ng-container *ngIf="!list.isReloading">
<span i18n *ngIf="list.selected.size == 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>&nbsp;<span i18n *ngIf="isFiltered">(filtered)</span>
<span i18n *ngIf="list.selected.size === 0">{list.collectionSize, plural, =1 {One document} other {{{list.collectionSize || 0}} documents}}</span>&nbsp;<span i18n *ngIf="isFiltered">(filtered)</span>
</ng-container>
</p>
<ngb-pagination *ngIf="list.collectionSize" [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
@ -113,52 +113,52 @@
</ng-container>
<ng-template #documentListNoError>
<div *ngIf="displayMode == 'largeCards'">
<div *ngIf="displayMode === 'largeCards'">
<app-document-card-large [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" *ngFor="let d of list.documents; trackBy: trackByDocumentId" [document]="d" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickDocumentType)="clickDocumentType($event)" (clickStoragePath)="clickStoragePath($event)" (clickMoreLike)="clickMoreLike(d.id)">
</app-document-card-large>
</div>
<table class="table table-sm align-middle border shadow-sm" *ngIf="displayMode == 'details'">
<table class="table table-sm align-middle border shadow-sm" *ngIf="displayMode === 'details'">
<thead>
<th></th>
<th class="d-none d-lg-table-cell"
sortable="archive_serial_number"
appSortable="archive_serial_number"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>ASN</th>
<th class="d-none d-md-table-cell"
sortable="correspondent__name"
appSortable="correspondent__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Correspondent</th>
<th
sortable="title"
appSortable="title"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Title</th>
<th class="d-none d-xl-table-cell"
sortable="document_type__name"
appSortable="document_type__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Document type</th>
<th class="d-none d-xl-table-cell"
sortable="storage_path__name"
appSortable="storage_path__name"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Storage path</th>
<th
sortable="created"
appSortable="created"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
i18n>Created</th>
<th class="d-none d-xl-table-cell"
sortable="added"
appSortable="added"
[currentSortField]="list.sortField"
[currentSortReverse]="list.sortReverse"
(sort)="onSort($event)"
@ -204,7 +204,7 @@
</tbody>
</table>
<div class="row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'">
<div class="row row-cols-paperless-cards" *ngIf="displayMode === 'smallCards'">
<app-document-card-small class="p-0" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)" (clickStoragePath)="clickStoragePath($event)" (clickDocumentType)="clickDocumentType($event)"></app-document-card-small>
</div>
<div *ngIf="list.documents?.length > 15" class="mt-3">

View File

@ -5,10 +5,10 @@
<div ngbDropdown>
<button class="btn btn-sm 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>
<button *ngFor="let t of textFilterTargets" ngbDropdownItem [class.active]="textFilterTarget === t.id" (click)="changeTextFilterTarget(t.id)">{{t.name}}</button>
</div>
</div>
<select *ngIf="textFilterTarget == 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
<select *ngIf="textFilterTarget === 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
<option *ngFor="let m of textFilterModifiers" ngbDropdownItem [value]="m.id">{{m.label}}</option>
</select>
<button *ngIf="_textFilter" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0 z-10" (click)="resetTextField()">
@ -16,7 +16,7 @@
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
</svg>
</button>
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget == 'fulltext-morelike'">
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget === 'fulltext-morelike'">
</div>
</div>
</div>

View File

@ -16,10 +16,10 @@
<table class="table table-striped align-middle border shadow-sm">
<thead>
<tr>
<th scope="col" sortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" class="d-none d-sm-table-cell" sortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" sortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
<th scope="col" *ngFor="let column of extraColumns" sortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
<th scope="col" appSortable="name" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Name</th>
<th scope="col" class="d-none d-sm-table-cell" appSortable="matching_algorithm" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Matching</th>
<th scope="col" appSortable="document_count" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)" i18n>Document count</th>
<th scope="col" *ngFor="let column of extraColumns" appSortable="{{column.key}}" [currentSortField]="sortField" [currentSortReverse]="sortReverse" (sort)="onSort($event)">{{column.name}}</th>
<th scope="col" i18n>Actions</th>
</tr>
</thead>

View File

@ -24,7 +24,7 @@
<div class="col">
<select class="form-select" formControlName="displayLanguage">
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale != 'en-US'"> - {{lang.englishName}}</span></option>
<option *ngFor="let lang of displayLanguageOptions" [ngValue]="lang.code">{{lang.name}}<span *ngIf="lang.code && currentLocale !== 'en-US'"> - {{lang.englishName}}</span></option>
</select>
<small *ngIf="displayLanguageIsDirty" class="form-text text-primary" i18n>You need to reload the page after applying a new language.</small>
@ -215,7 +215,7 @@
</div>
</div>
<div *ngIf="savedViews && savedViews.length == 0" i18n>No saved views defined.</div>
<div *ngIf="savedViews && savedViews.length === 0" i18n>No saved views defined.</div>
<div *ngIf="!savedViews">
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
@ -265,7 +265,7 @@
</div>
</li>
<div *ngIf="mailAccounts.length == 0" i18n>No mail accounts defined.</div>
<div *ngIf="mailAccounts.length === 0" i18n>No mail accounts defined.</div>
</ul>
</ng-container>
@ -302,7 +302,7 @@
</div>
</li>
<div *ngIf="mailRules.length == 0" i18n>No mail rules defined.</div>
<div *ngIf="mailRules.length === 0" i18n>No mail rules defined.</div>
</ul>
</ng-container>
</ng-container>
@ -404,5 +404,5 @@
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<button type="submit" class="btn btn-primary mb-2" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="!(isDirty$ | async)" i18n>Save</button>
<button type="submit" class="btn btn-primary mb-2" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="(isDirty$ | async) === false" i18n>Save</button>
</form>

View File

@ -1,11 +1,11 @@
<app-page-header title="File Tasks" i18n-title>
<div class="btn-toolbar col col-md-auto">
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedTasks.size == 0">
<button class="btn btn-sm btn-outline-secondary me-2" (click)="clearSelection()" [hidden]="selectedTasks.size === 0">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>&nbsp;<ng-container i18n>Clear selection</ng-container>
</button>
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total == 0">
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total === 0">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
</svg>&nbsp;<ng-container i18n>{{dismissButtonText}}</ng-container>
@ -33,13 +33,13 @@
<tr>
<th scope="col">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="all-tasks" [disabled]="currentTasks.length == 0" (click)="toggleAll($event); $event.stopPropagation();">
<input type="checkbox" class="form-check-input" id="all-tasks" [disabled]="currentTasks.length === 0" (click)="toggleAll($event); $event.stopPropagation();">
<label class="form-check-label" for="all-tasks"></label>
</div>
</th>
<th scope="col" i18n>Name</th>
<th scope="col" class="d-none d-lg-table-cell" i18n>Created</th>
<th scope="col" class="d-none d-lg-table-cell" *ngIf="activeTab != 'started' && activeTab != 'queued'" i18n>Results</th>
<th scope="col" class="d-none d-lg-table-cell" *ngIf="activeTab !== 'started' && activeTab !== 'queued'" i18n>Results</th>
<th scope="col" class="d-table-cell d-lg-none" i18n>Info</th>
<th scope="col" i18n>Actions</th>
</tr>
@ -55,7 +55,7 @@
</th>
<td class="overflow-auto">{{ task.task_file_name }}</td>
<td class="d-none d-lg-table-cell">{{ task.date_created | customDate:'short' }}</td>
<td class="d-none d-lg-table-cell" *ngIf="activeTab != 'started' && activeTab != 'queued'">
<td class="d-none d-lg-table-cell" *ngIf="activeTab !== 'started' && activeTab !== 'queued'">
<div *ngIf="task.result?.length > 50" class="result" (click)="expandTask(task); $event.stopPropagation();"
[ngbPopover]="resultPopover" popoverClass="shadow small mobile" triggers="mouseenter:mouseleave" container="body">
<span class="small d-none d-md-inline-block font-monospace text-muted">{{ task.result | slice:0:50 }}&hellip;</span>
@ -91,7 +91,7 @@
</td>
</tr>
<tr>
<td class="p-0" [class.border-0]="expandedTask != task.id" colspan="5">
<td class="p-0" [class.border-0]="expandedTask !== task.id" colspan="5">
<pre #collapse="ngbCollapse" [ngbCollapse]="expandedTask !== task.id" class="small mb-0"><div class="small p-1 p-lg-3 ms-lg-3">{{ task.result }}</div></pre>
</td>
</tr>

View File

@ -1,12 +1,10 @@
import { Component, OnInit } from '@angular/core'
import { Component } from '@angular/core'
@Component({
selector: 'app-not-found',
templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.scss'],
})
export class NotFoundComponent implements OnInit {
export class NotFoundComponent {
constructor() {}
ngOnInit(): void {}
}

View File

@ -1,4 +1,11 @@
import { Directive, EventEmitter, Input, Output } from '@angular/core'
import {
Directive,
EventEmitter,
HostBinding,
HostListener,
Input,
Output,
} from '@angular/core'
export interface SortEvent {
column: string
@ -6,18 +13,13 @@ export interface SortEvent {
}
@Directive({
selector: 'th[sortable]',
host: {
'[class.asc]': 'currentSortField == sortable && !currentSortReverse',
'[class.des]': 'currentSortField == sortable && currentSortReverse',
'(click)': 'rotate()',
},
selector: 'th[appSortable]',
})
export class SortableDirective {
constructor() {}
@Input()
sortable: string = ''
appSortable: string = ''
@Input()
currentSortReverse: boolean = false
@ -27,11 +29,20 @@ export class SortableDirective {
@Output() sort = new EventEmitter<SortEvent>()
rotate() {
if (this.currentSortField != this.sortable) {
this.sort.emit({ column: this.sortable, reverse: false })
@HostBinding('class.asc') get asc() {
return (
this.currentSortField === this.appSortable && !this.currentSortReverse
)
}
@HostBinding('class.des') get des() {
return this.currentSortField === this.appSortable && this.currentSortReverse
}
@HostListener('click') rotate() {
if (this.currentSortField != this.appSortable) {
this.sort.emit({ column: this.appSortable, reverse: false })
} else if (
this.currentSortField == this.sortable &&
this.currentSortField == this.appSortable &&
!this.currentSortReverse
) {
this.sort.emit({ column: this.currentSortField, reverse: true })

View File

@ -1,152 +0,0 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
"deprecation": {
"severity": "warning"
},
"eofline": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"typedef": [
true,
"call-signature"
],
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -4,6 +4,7 @@ from guardian.admin import GuardedModelAdmin
from .models import Correspondent
from .models import Document
from .models import DocumentType
from .models import PaperlessTask
from .models import SavedView
from .models import SavedViewFilterRule
from .models import StoragePath
@ -113,9 +114,27 @@ class StoragePathAdmin(GuardedModelAdmin):
list_editable = ("path", "match", "matching_algorithm")
class TaskAdmin(admin.ModelAdmin):
list_display = ("task_id", "task_file_name", "task_name", "date_done", "status")
list_filter = ("status", "date_done", "task_file_name", "task_name")
search_fields = ("task_name", "task_id", "status")
readonly_fields = (
"task_id",
"task_file_name",
"task_name",
"status",
"date_created",
"date_started",
"date_done",
"result",
)
admin.site.register(Correspondent, CorrespondentAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(DocumentType, DocumentTypeAdmin)
admin.site.register(Document, DocumentAdmin)
admin.site.register(SavedView, SavedViewAdmin)
admin.site.register(StoragePath, StoragePathAdmin)
admin.site.register(PaperlessTask, TaskAdmin)

View File

@ -2,6 +2,7 @@ import hashlib
import json
import os
import shutil
import tempfile
import time
import tqdm
@ -12,6 +13,7 @@ from django.core import serializers
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.db import transaction
from django.utils import timezone
from documents.models import Comment
from documents.models import Correspondent
from documents.models import Document
@ -76,6 +78,7 @@ class Command(BaseCommand):
"do not belong to the current export, such as files from "
"deleted documents.",
)
parser.add_argument(
"--no-progress-bar",
default=False,
@ -83,6 +86,14 @@ class Command(BaseCommand):
help="If set, the progress bar will not be shown",
)
parser.add_argument(
"-z",
"--zip",
default=False,
action="store_true",
help="Export the documents to a zip file in the given directory",
)
def __init__(self, *args, **kwargs):
BaseCommand.__init__(self, *args, **kwargs)
self.target = None
@ -98,6 +109,19 @@ class Command(BaseCommand):
self.compare_checksums = options["compare_checksums"]
self.use_filename_format = options["use_filename_format"]
self.delete = options["delete"]
zip_export: bool = options["zip"]
# If zipping, save the original target for later and
# get a temporary directory for the target
temp_dir = None
original_target = None
if zip_export:
original_target = self.target
temp_dir = tempfile.TemporaryDirectory(
dir=settings.SCRATCH_DIR,
prefix="paperless-export",
)
self.target = temp_dir.name
if not os.path.exists(self.target):
raise CommandError("That path doesn't exist")
@ -105,8 +129,26 @@ class Command(BaseCommand):
if not os.access(self.target, os.W_OK):
raise CommandError("That path doesn't appear to be writable")
with FileLock(settings.MEDIA_LOCK):
self.dump(options["no_progress_bar"])
try:
with FileLock(settings.MEDIA_LOCK):
self.dump(options["no_progress_bar"])
# We've written everything to the temporary directory in this case,
# now make an archive in the original target, with all files stored
if zip_export:
shutil.make_archive(
os.path.join(
original_target,
f"export-{timezone.localdate().isoformat()}",
),
format="zip",
root_dir=temp_dir.name,
)
finally:
# Always cleanup the temporary directory, if one was created
if zip_export and temp_dir is not None:
temp_dir.cleanup()
def dump(self, progress_bar_disable=False):
# 1. Take a snapshot of what files exist in the current export folder

View File

@ -5,10 +5,12 @@ import shutil
import tempfile
from pathlib import Path
from unittest import mock
from zipfile import ZipFile
from django.core.management import call_command
from django.test import override_settings
from django.test import TestCase
from django.utils import timezone
from documents.management.commands import document_exporter
from documents.models import Comment
from documents.models import Correspondent
@ -365,3 +367,74 @@ class TestExportImport(DirectoriesMixin, TestCase):
mime_type="application/pdf",
)
self.assertRaises(FileNotFoundError, call_command, "document_exporter", target)
@override_settings(PASSPHRASE="test")
def test_export_zipped(self):
"""
GIVEN:
- Request to export documents to zipfile
WHEN:
- Documents are exported
THEN:
- Zipfile is created
- Zipfile contains exported files
"""
shutil.rmtree(os.path.join(self.dirs.media_dir, "documents"))
shutil.copytree(
os.path.join(os.path.dirname(__file__), "samples", "documents"),
os.path.join(self.dirs.media_dir, "documents"),
)
args = ["document_exporter", self.target, "--zip"]
call_command(*args)
expected_file = os.path.join(
self.target,
f"export-{timezone.localdate().isoformat()}.zip",
)
self.assertTrue(os.path.isfile(expected_file))
with ZipFile(expected_file) as zip:
self.assertEqual(len(zip.namelist()), 11)
self.assertIn("manifest.json", zip.namelist())
self.assertIn("version.json", zip.namelist())
@override_settings(PASSPHRASE="test")
def test_export_zipped_format(self):
"""
GIVEN:
- Request to export documents to zipfile
- Export is following filename formatting
WHEN:
- Documents are exported
THEN:
- Zipfile is created
- Zipfile contains exported files
"""
shutil.rmtree(os.path.join(self.dirs.media_dir, "documents"))
shutil.copytree(
os.path.join(os.path.dirname(__file__), "samples", "documents"),
os.path.join(self.dirs.media_dir, "documents"),
)
args = ["document_exporter", self.target, "--zip", "--use-filename-format"]
with override_settings(
FILENAME_FORMAT="{created_year}/{correspondent}/{title}",
):
call_command(*args)
expected_file = os.path.join(
self.target,
f"export-{timezone.localdate().isoformat()}.zip",
)
self.assertTrue(os.path.isfile(expected_file))
with ZipFile(expected_file) as zip:
# Extras are from the directories, which also appear in the listing
self.assertEqual(len(zip.namelist()), 14)
self.assertIn("manifest.json", zip.namelist())
self.assertIn("version.json", zip.namelist())