Compare commits

..

4 Commits

Author SHA1 Message Date
shamoon
23f6a8b708 version_index wf/file placeholder 2026-03-01 12:23:51 -08:00
shamoon
9b469c4f4f tests 2026-03-01 12:23:45 -08:00
shamoon
af0212ff9e index prop 2026-03-01 12:23:44 -08:00
Jan Kleine
82d8f48e9b Enhancement: add version label filename placeholder (#12185)
* Enhancement: add version label filename placeholder

* fix test

* add workflow placeholder

* docs and missing version_label

* typo

* fix consume placeholder

* update docs

* Apply suggestion from @shamoon

* fix None value

---------

Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2026-03-01 12:16:32 -08:00
91 changed files with 5319 additions and 3281 deletions

View File

@@ -35,18 +35,18 @@ jobs:
fail-fast: false
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Start containers
run: |
docker compose --file docker/compose/docker-compose.ci-test.yml pull --quiet
docker compose --file docker/compose/docker-compose.ci-test.yml up --detach
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@v6
with:
python-version: "${{ matrix.python-version }}"
- name: Install uv
uses: astral-sh/setup-uv@v7.3.1
uses: astral-sh/setup-uv@v7
with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
@@ -83,13 +83,13 @@ jobs:
pytest
- name: Upload test results to Codecov
if: always()
uses: codecov/codecov-action@v5.5.2
uses: codecov/codecov-action@v5
with:
flags: backend-python-${{ matrix.python-version }}
files: junit.xml
report_type: test_results
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.5.2
uses: codecov/codecov-action@v5
with:
flags: backend-python-${{ matrix.python-version }}
files: coverage.xml
@@ -106,14 +106,14 @@ jobs:
DEFAULT_PYTHON: "3.12"
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.1
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6.2.0
with:
python-version: "${{ env.DEFAULT_PYTHON }}"
- name: Install uv
uses: astral-sh/setup-uv@v7.3.1
uses: astral-sh/setup-uv@v7.2.1
with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true

View File

@@ -41,7 +41,7 @@ jobs:
ref-name: ${{ steps.ref.outputs.name }}
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6.0.1
- name: Determine ref name
id: ref
run: |
@@ -130,7 +130,7 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6.19.2
uses: docker/build-push-action@v6.18.0
with:
context: .
file: ./Dockerfile
@@ -152,7 +152,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
if: steps.check-push.outputs.should-push == 'true'
uses: actions/upload-artifact@v7.0.0
uses: actions/upload-artifact@v6.0.0
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
@@ -168,7 +168,7 @@ jobs:
packages: write
steps:
- name: Download digests
uses: actions/download-artifact@v8.0.0
uses: actions/download-artifact@v7.0.0
with:
path: /tmp/digests
pattern: digests-*

View File

@@ -33,16 +33,16 @@ jobs:
name: Build Documentation
runs-on: ubuntu-24.04
steps:
- uses: actions/configure-pages@v5.0.0
- uses: actions/configure-pages@v5
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@v6
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v7.3.1
uses: astral-sh/setup-uv@v7
with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
@@ -58,7 +58,7 @@ jobs:
--frozen \
zensical build --clean
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v4.0.0
uses: actions/upload-pages-artifact@v4
with:
path: site
name: github-pages-${{ github.run_id }}-${{ github.run_attempt }}
@@ -72,7 +72,7 @@ jobs:
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy GitHub Pages
uses: actions/deploy-pages@v4.0.5
uses: actions/deploy-pages@v4
id: deployment
with:
artifact_name: github-pages-${{ github.run_id }}-${{ github.run_attempt }}

View File

@@ -22,20 +22,20 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
id: cache-frontend-deps
uses: actions/cache@v5.0.3
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
@@ -49,19 +49,19 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
uses: actions/cache@v5.0.3
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
@@ -83,19 +83,19 @@ jobs:
shard-count: [4]
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
uses: actions/cache@v5.0.3
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
@@ -107,13 +107,13 @@ jobs:
run: cd src-ui && pnpm run test --max-workers=2 --shard=${{ matrix.shard-index }}/${{ matrix.shard-count }}
- name: Upload test results to Codecov
if: always()
uses: codecov/codecov-action@v5.5.2
uses: codecov/codecov-action@v5
with:
flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/
report_type: test_results
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5.5.2
uses: codecov/codecov-action@v5
with:
flags: frontend-node-${{ matrix.node-version }}
directory: src-ui/coverage/
@@ -133,19 +133,19 @@ jobs:
shard-count: [2]
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
uses: actions/cache@v5.0.3
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
@@ -163,19 +163,19 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
uses: actions/cache@v5.0.3
uses: actions/cache@v5
with:
path: |
~/.pnpm-store

View File

@@ -28,14 +28,14 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
# ---- Frontend Build ----
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
@@ -47,11 +47,11 @@ jobs:
# ---- Backend Setup ----
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@v6
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v7.3.1
uses: astral-sh/setup-uv@v7
with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
@@ -118,7 +118,7 @@ jobs:
sudo chown -R 1000:1000 paperless-ngx/
tar -cJf paperless-ngx.tar.xz paperless-ngx/
- name: Upload release artifact
uses: actions/upload-artifact@v7.0.0
uses: actions/upload-artifact@v6
with:
name: release
path: dist/paperless-ngx.tar.xz
@@ -133,7 +133,7 @@ jobs:
version: ${{ steps.get-version.outputs.version }}
steps:
- name: Download release artifact
uses: actions/download-artifact@v8.0.0
uses: actions/download-artifact@v7
with:
name: release
path: ./
@@ -148,7 +148,7 @@ jobs:
fi
- name: Create release and changelog
id: create-release
uses: release-drafter/release-drafter@v6.2.0
uses: release-drafter/release-drafter@v6
with:
name: Paperless-ngx ${{ steps.get-version.outputs.version }}
tag: ${{ steps.get-version.outputs.version }}
@@ -159,7 +159,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload release archive
uses: shogo82148/actions-upload-release-asset@v1.9.2
uses: shogo82148/actions-upload-release-asset@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
upload_url: ${{ steps.create-release.outputs.upload_url }}
@@ -176,16 +176,16 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
with:
ref: main
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@v6
with:
python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
- name: Install uv
uses: astral-sh/setup-uv@v7.3.1
uses: astral-sh/setup-uv@v7
with:
version: ${{ env.DEFAULT_UV_VERSION }}
enable-cache: true
@@ -218,7 +218,7 @@ jobs:
git commit -am "Changelog ${{ needs.publish-release.outputs.version }} - GHA"
git push origin ${{ needs.publish-release.outputs.version }}-changelog
- name: Create pull request
uses: actions/github-script@v8.0.0
uses: actions/github-script@v8
with:
script: |
const { repo, owner } = context.repo;

View File

@@ -34,10 +34,10 @@ jobs:
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4.32.5
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -45,4 +45,4 @@ jobs:
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.32.5
uses: github/codeql-action/analyze@v4

View File

@@ -13,11 +13,11 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
with:
token: ${{ secrets.PNGX_BOT_PAT }}
- name: crowdin action
uses: crowdin/github-action@v2.15.0
uses: crowdin/github-action@v2
with:
upload_translations: false
download_translations: true

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Label PR by file path or branch name
# see .github/labeler.yml for the labeler config
uses: actions/labeler@v6.0.1
uses: actions/labeler@v6
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Label by size
@@ -26,7 +26,7 @@ jobs:
fail_if_xl: 'false'
excluded_files: /\.lock$/ /\.txt$/ ^src-ui/pnpm-lock\.yaml$ ^src-ui/messages\.xlf$ ^src/locale/en_US/LC_MESSAGES/django\.po$
- name: Label by PR title
uses: actions/github-script@v8.0.0
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
@@ -52,7 +52,7 @@ jobs:
}
- name: Label bot-generated PRs
if: ${{ contains(github.actor, 'dependabot') || contains(github.actor, 'crowdin-bot') }}
uses: actions/github-script@v8.0.0
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
@@ -77,7 +77,7 @@ jobs:
}
- name: Welcome comment
if: ${{ !contains(github.actor, 'bot') }}
uses: actions/github-script@v8.0.0
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;

View File

@@ -19,6 +19,6 @@ jobs:
if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot'
steps:
- name: Label PR with release-drafter
uses: release-drafter/release-drafter@v6.2.0
uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -15,7 +15,7 @@ jobs:
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04
steps:
- uses: actions/stale@v10.2.0
- uses: actions/stale@v10
with:
days-before-stale: 7
days-before-close: 14
@@ -37,7 +37,7 @@ jobs:
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04
steps:
- uses: dessant/lock-threads@v6.0.0
- uses: dessant/lock-threads@v6
with:
issue-inactive-days: '30'
pr-inactive-days: '30'
@@ -57,7 +57,7 @@ jobs:
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v8.0.0
- uses: actions/github-script@v8
with:
script: |
function sleep(ms) {
@@ -114,7 +114,7 @@ jobs:
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v8.0.0
- uses: actions/github-script@v8
with:
script: |
function sleep(ms) {
@@ -206,7 +206,7 @@ jobs:
if: github.repository_owner == 'paperless-ngx'
runs-on: ubuntu-24.04
steps:
- uses: actions/github-script@v8.0.0
- uses: actions/github-script@v8
with:
script: |
function sleep(ms) {

View File

@@ -11,7 +11,7 @@ jobs:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
env:
GH_REF: ${{ github.ref }} # sonar rule:githubactions:S7630 - avoid injection
with:
@@ -19,13 +19,13 @@ jobs:
ref: ${{ env.GH_REF }}
- name: Set up Python
id: setup-python
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@v6
- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq --no-install-recommends gettext
- name: Install uv
uses: astral-sh/setup-uv@v7.3.1
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Install backend python dependencies
@@ -36,18 +36,18 @@ jobs:
- name: Generate backend translation strings
run: cd src/ && uv run manage.py makemessages -l en_US -i "samples*"
- name: Install pnpm
uses: pnpm/action-setup@v4.2.0
uses: pnpm/action-setup@v4
with:
version: 10
- name: Use Node.js 24
uses: actions/setup-node@v6.2.0
uses: actions/setup-node@v6
with:
node-version: 24.x
cache: 'pnpm'
cache-dependency-path: 'src-ui/pnpm-lock.yaml'
- name: Cache frontend dependencies
id: cache-frontend-deps
uses: actions/cache@v5.0.3
uses: actions/cache@v5
with:
path: |
~/.pnpm-store
@@ -63,7 +63,7 @@ jobs:
cd src-ui
pnpm run ng extract-i18n
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v7.1.0
uses: stefanzweifel/git-auto-commit-action@v7
with:
file_pattern: 'src-ui/messages.xlf src/locale/en_US/LC_MESSAGES/django.po'
commit_message: "Auto translate strings"

View File

@@ -45,7 +45,7 @@ ENV \
ARG TARGETARCH
ARG TARGETVARIANT
# Lock this version
ARG S6_OVERLAY_VERSION=3.2.2.0
ARG S6_OVERLAY_VERSION=3.2.1.0
ARG S6_BUILD_TIME_PKGS="curl \
xz-utils"

View File

@@ -7,9 +7,9 @@
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/docs/assets/logo_full_white.png" width="50%">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/docs/assets/logo_full_black.png" width="50%">
<img src="https://github.com/paperless-ngx/paperless-ngx/blob/main/docs/assets/logo_full_black.png" width="50%">
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/paperless-ngx/paperless-ngx/blob/main/resources/logo/web/png/White%20logo%20-%20no%20background.png" width="50%">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
<img src="https://github.com/paperless-ngx/paperless-ngx/raw/main/resources/logo/web/png/Black%20logo%20-%20no%20background.png" width="50%">
</picture>
</p>

View File

@@ -4,7 +4,7 @@
# correct networking for the tests
services:
gotenberg:
image: docker.io/gotenberg/gotenberg:8.27
image: docker.io/gotenberg/gotenberg:8.26
hostname: gotenberg
container_name: gotenberg
network_mode: host

View File

@@ -72,7 +72,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.27
image: docker.io/gotenberg/gotenberg:8.26
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.

View File

@@ -66,7 +66,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.27
image: docker.io/gotenberg/gotenberg:8.26
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.

View File

@@ -55,7 +55,7 @@ services:
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
PAPERLESS_TIKA_ENDPOINT: http://tika:9998
gotenberg:
image: docker.io/gotenberg/gotenberg:8.27
image: docker.io/gotenberg/gotenberg:8.26
restart: unless-stopped
# The gotenberg chromium route is used to convert .eml files. We do not
# want to allow external content like tracking pixels or even javascript.

View File

@@ -332,6 +332,7 @@ Paperless provides the following variables for use within filenames:
- `{{ owner_username }}`: Username of document owner, if any, or "none"
- `{{ original_name }}`: Document original filename, minus the extension, if any, or "none"
- `{{ doc_pk }}`: The paperless identifier (primary key) for the document.
- `{{ version_label }}`: The document version label or "none" if not explicitly set.
!!! warning

Binary file not shown.

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 768 B

12
docs/assets/logo.svg Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M299,891.7c-4.2-19.8-12.5-59.6-13.6-59.6c-176.7-105.7-155.8-288.7-97.3-393.4
c12.5,131.8,245.8,222.8,109.8,383.9c-1.1,2,6.2,27.2,12.5,50.2c27.2-46,68-101.4,65.8-106.7C208.9,358.2,731.9,326.9,840.6,73.7
c49.1,244.8-25.1,623.5-445.5,719.7c-2,1.1-76.3,131.8-79.5,132.9c0-2-31.4-1.1-27.2-11.5C290.7,908.4,294.8,900.1,299,891.7
L299,891.7z M293.8,793.4c53.3-61.8-9.4-167.4-47.1-201.9C310.5,701.3,306.3,765.1,293.8,793.4L293.8,793.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -1,19 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2670 860">
<path id="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g id="text" style="fill: #000;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2962.2 860.2" style="enable-background:new 0 0 2962.2 860.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#17541F;stroke:#000000;stroke-miterlimit:10;}
</style>
<path d="M1055.6,639.7v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3v249.6L1055.6,639.7L1055.6,639.7z M1059.1,514.9c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
c16.8,0,30.3-5.9,40.6-17.7C1054,546.9,1059.1,532.3,1059.1,514.9z"/>
<path d="M1417.8,398.2c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V390.2h85.3v20.6
c18-20,43.1-30.1,75.4-30.1C1379.2,380.7,1399.5,386.6,1417.8,398.2z M1389.5,514.9c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7S1389.5,532.3,1389.5,514.9z"/>
<path d="M1713.6,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2s37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8c3.6,11.4,10.5,20.7,20.9,28.1
c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1695.8,570.1,1704.9,563.7,1713.6,555.3z M1596.9,486.2h92.9
c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11S1599,473.9,1596.9,486.2z"/>
<path d="M1908.8,418.4c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V390.2h85.3V418.4L1908.8,418.4z"/>
<path d="M2113,258.2v381.5h-85.3V258.2H2113z"/>
<path d="M2360.8,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2343.1,570.1,2352.1,563.7,2360.8,555.3z
M2244.1,486.2h92.9c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11C2251.7,464.1,2246.2,473.9,2244.1,486.2z"/>
<path d="M2565.9,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2590.5,448.7,2577.6,446.3,2565.9,446.3z"/>
<path d="M2817.3,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2841.8,448.7,2828.9,446.3,2817.3,446.3z"/>
<g>
<path d="M2508,724h60.2v17.3H2508V724z"/>
<path d="M2629.2,694.4c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6v-52.6
c0-9.3-1.7-16.2-5.1-20.7c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2620.2,699.4,2624.4,696.4,2629.2,694.4z"/>
<path d="M2790.3,833.2c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2s-6.6-11.9-7.1-19.6h19.6
c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7c-3.6,3.4-8,6.2-13.3,8.2
c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6s2-17.3,6-24.7
s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2803.2,815.9,2798.9,826.4,2790.3,833.2z M2782.2,755.7c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2776.1,764.1,2779.6,760.4,2782.2,755.7z"/>
<path d="M2843.5,788.4h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2843.5,788.4z
"/>
</g>
<path d="M835.8,319.2c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2H647v-135h72.7c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7c11.5-18.9,17.3-39.5,17.3-61.9
C853.1,358.9,847.4,338.1,835.8,319.2z M747,416.6c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C761.1,396.6,756.4,407.7,747,416.6z"/>
<path class="st0" d="M164.7,698.7c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4C82.8,431.4,277,507.1,163.8,641.2
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C89.7,254.7,525,228.6,615.5,17.9c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C157.8,712.6,161.2,705.7,164.7,698.7L164.7,698.7z M160.4,616.9
c44.4-51.4-7.8-139.3-39.2-168C174.3,540.2,170.8,593.3,160.4,616.9L160.4,616.9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2670 860">
<path id="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g id="text" style="fill: #eee;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1,19 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 2670 860">
<path id="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g id="text" style="fill: #fff;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 2962.2 860.2" style="enable-background:new 0 0 2962.2 860.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;stroke:#000000;stroke-miterlimit:10;}
.st1{fill:#17541F;stroke:#000000;stroke-miterlimit:10;}
</style>
<path class="st0" d="M1055.6,639.7v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3v249.6L1055.6,639.7L1055.6,639.7z M1059.1,514.9c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
c16.8,0,30.3-5.9,40.6-17.7C1054,546.9,1059.1,532.3,1059.1,514.9z"/>
<path class="st0" d="M1417.8,398.2c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V390.2h85.3v20.6
c18-20,43.1-30.1,75.4-30.1C1379.2,380.7,1399.5,386.6,1417.8,398.2z M1389.5,514.9c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7S1389.5,532.3,1389.5,514.9z"/>
<path class="st0" d="M1713.6,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2s37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8c3.6,11.4,10.5,20.7,20.9,28.1
c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1695.8,570.1,1704.9,563.7,1713.6,555.3z M1596.9,486.2h92.9
c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11S1599,473.9,1596.9,486.2z"/>
<path class="st0" d="M1908.8,418.4c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7
c-8.4-2.1-15.7-3.1-22-3.1c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V390.2h85.3V418.4L1908.8,418.4z"/>
<path class="st0" d="M2113,258.2v381.5h-85.3V258.2H2113z"/>
<path class="st0" d="M2360.8,555.3l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2343.1,570.1,2352.1,563.7,2360.8,555.3z
M2244.1,486.2h92.9c-2.1-12.3-7.5-22.1-16.2-29.4s-18.7-11-30.1-11s-21.5,3.7-30.3,11C2251.7,464.1,2246.2,473.9,2244.1,486.2z"/>
<path class="st0" d="M2565.9,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2590.5,448.7,2577.6,446.3,2565.9,446.3z"/>
<path class="st0" d="M2817.3,446.3c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2841.8,448.7,2828.9,446.3,2817.3,446.3z"/>
<g>
<path class="st0" d="M2508,724h60.2v17.3H2508V724z"/>
<path class="st0" d="M2629.2,694.4c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6v-52.6
c0-9.3-1.7-16.2-5.1-20.7c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2620.2,699.4,2624.4,696.4,2629.2,694.4z"/>
<path class="st0" d="M2790.3,833.2c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2s-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2803.2,815.9,2798.9,826.4,2790.3,833.2z M2782.2,755.7c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2776.1,764.1,2779.6,760.4,2782.2,755.7z"/>
<path class="st0" d="M2843.5,788.4h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35
L2843.5,788.4z"/>
</g>
<path class="st0" d="M835.8,319.2c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2H647v-135h72.7c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7c11.5-18.9,17.3-39.5,17.3-61.9
C853.1,358.9,847.4,338.1,835.8,319.2z M747,416.6c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C761.1,396.6,756.4,407.7,747,416.6z"/>
<path class="st1" d="M164.7,698.7c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4C82.8,431.4,277,507.1,163.8,641.2
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C89.7,254.7,525,228.6,615.5,17.9c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C157.8,712.6,161.2,705.7,164.7,698.7L164.7,698.7z M160.4,616.9
c44.4-51.4-7.8-139.3-39.2-168C174.3,540.2,170.8,593.3,160.4,616.9L160.4,616.9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1000 1000">
<defs>
<style>
.st0 {
fill: #005616;
}
</style>
</defs>
<path class="st0" d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 644 B

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1000 1000">
<defs>
<style>
.st0 {
fill: #fff;
}
</style>
</defs>
<path class="st0" d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 641 B

View File

@@ -4,7 +4,7 @@ title: Home
<div class="grid-left" markdown>
![image](assets/logo_full_black.svg#only-light){.index-logo}
![image](assets/logo_full_eee.svg#only-dark){.index-logo}
![image](assets/logo_full_white.svg#only-dark){.index-logo}
**Paperless-ngx** is a _community-supported_ open-source document management system that transforms your
physical documents into a searchable online archive so you can keep, well, _less paper_.

View File

@@ -616,8 +616,9 @@ applied. You can use the following placeholders in the template with any trigger
- `{{added_day}}`: added day
- `{{added_time}}`: added time in HH:MM format
- `{{original_filename}}`: original file name without extension
- `{{filename}}`: current file name without extension (for "added" workflows this may not be final yet, you can use `{{original_filename}}`)
- `{{filename}}`: current file name without extension
- `{{doc_title}}`: current document title (cannot be used in title assignment)
- `{{version_label}}`: the document version label (empty if not explicitly set)
The following placeholders are only available for "added" or "updated" triggers

16
resources/logo.txt Normal file
View File

@@ -0,0 +1,16 @@
9w
{@@N
Q@@@@H
G@@@@@@@\
SilN@@@@@@@
*Q *@@@@@@@@S /= = = = = = = = = = = = = = = = = =\
*@ B@@@@@@@@N || ||
N R$ A@@@@@@@@@@ || PAPERLESS-NGX ||
x@@ $U B@@@@@@@@@R || ||
N@@N^ @ N@@@@@@@@@* \= = = = = = = = = = = = = = = = = =/
|@@@u @ E@@@@@@@@l
Q@@@ \ Px@@@@@@P
1@@S` @@@o'
z$ ;
v
/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,329 @@
%PDF-1.6
%âãÏÓ
3 0 obj
<</Metadata 7 0 R/OCProperties<</D<</ON[8 0 R 22 0 R]/Order 23 0 R/RBGroups[]>>/OCGs[8 0 R 22 0 R]>>/Pages 4 0 R/Type/Catalog>>
endobj
7 0 obj
<</Length 8109/Subtype/XML/Type/Metadata>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 7.1-c000 79.a8731b9, 2021/09/09-00:37:38 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/"
xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/">
<xmp:CreateDate>2018-12-29T21:47:38Z</xmp:CreateDate>
<xmp:CreatorTool>Chromium</xmp:CreatorTool>
<xmp:ModifyDate>2022-02-26T20:11:14-08:00</xmp:ModifyDate>
<xmp:MetadataDate>2022-02-26T20:11:14-08:00</xmp:MetadataDate>
<xmp:Thumbnails>
<rdf:Alt>
<rdf:li rdf:parseType="Resource">
<xmpGImg:width>256</xmpGImg:width>
<xmpGImg:height>76</xmpGImg:height>
<xmpGImg:format>JPEG</xmpGImg:format>
<xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA&#xA;AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK&#xA;DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f&#xA;Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgATAEAAwER&#xA;AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA&#xA;AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB&#xA;UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE&#xA;1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ&#xA;qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy&#xA;obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp&#xA;0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo&#xA;+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYqlnmXzJ&#xA;pHlvR7jV9VmENpAP9k7n7MaD9pm7DBKQAssZSAFl5x+RvnjUfN2r+br+8+BWmtJLaCtVijZZUVB8&#xA;liFT3O+VYp8RLVhnxEvWsub3Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FWG+f/zU8seTLdlvJfrWqMKwaZCw9U1Gxc7iNfdvoByueQRa55BF8t+evzB8&#xA;w+c9T+t6pLxgjJFpYx1EMKn+Ud2PdjufltmJOZlzcOczI7vVP+cVYSbjzJNX4VSzSnuxmP8Axrl2&#xA;n6t2m6voLMlynYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7&#xA;FVk88FvBJPPIsMESl5ZZCFRVUVLMx2AA74q8A/Mv/nIqV2l0ryYeEYqkusuPiPY/V0Ybf67fQB1z&#xA;GyZugcXJn6B4TcXFxczyXFxK808rF5ZZGLuzHclmNSScxnGU8VfTH/OMOlNB5R1LUXFDfXnBPdII&#xA;wAf+CkYZl4Bs5enGz2TL3IdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVYj5X/MGw13zf5i8vRFRJosiJDv8UqgcJ2/55zfD92QjOyQ1xnZIZdk2x8v/np+a1zr2qTe&#xA;XNJmMeh2MhjuHQ0+tTIaMWI6xow+EdD9rwpiZclmhycPNks0OTyPKGh2Kr4YZZ5khhQySysEjjUV&#xA;ZmY0AA8ScVfbnkXy2nlryjpeiinqWkAE5HQzPV5SPnIxzYQjQp2MI0KT3JMnYq7FXYq7FXYq7FXY&#xA;q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXw9oPm/WdE8zx+Y7OWl+szTSg14SCQ1kj&#xA;cd1etD/XNeJEG3XRkQbfUt9+Ytlqv5U6n5q0ZykqWcw9Ov7y3uuPHi3TdGYH3G/fMwzuNhzDkuNh&#xA;8f5guC7FXYq9q/5x5/LaXUNTTzdqUVNPsWP6NRx/e3A29QA/sxdj/N8jmRhhe7kYMdmy+ksynLdi&#xA;rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir4a836BP5f8AM2pa&#xA;NOpVrOd40r+1HWsbivZkIYZr5CjTrpRo0y3ytqd6dCuLK3J9DzFYXmn3cP8ANe6dCLi3kUdjJG6R&#xA;e5qcnE7e9nE7e953lTU7FXrv5W/kPquuzQ6r5kiew0RSHS1eqXFyOwpsY4z3Y7n9nryF+PCTuW/H&#xA;hJ3PJ9NWlpbWltFa2sSwW0CiOGGMBURFFAqgdAMywHMAVcVdirsVdirsVdirsVdirsVdirsVdirs&#xA;VdirsVdirsVdirsVdirsVdirsVdirsVdiryr86/yjfzbbprOjKq69aJwaI0UXMQ3Ccugdf2SevQ9&#xA;qU5cfFuObRlxcW45vL/ym0y40i+vNc8xwvZaP5Raa5uopk4yPezxCGKDi1DypuPeleuU4xW56NOI&#xA;VueiJ8n/APOP2q+Z7WLXbq6j0XStQLT2tmqNNOsLMSgoxRQCv2TyO29MMcJO6Y4Cd3snk78mvI3l&#xA;Z0uLa0N7qKUIvrykrqw7otAifNVr75fHEA5EcUYs4yxsdirsVdirsVdirsVdirsVdirsVdirsVdi&#xA;rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVeRedfy/wBa8z/mjBbyQyxeUJYra81aQlfSnntP&#xA;URUWhrUpIqUNNqnsMolAmXk0TgTLyeuIiIioihUUBVVRQADYAAZe3t4q7FXYq7FXYq7FXYq7FXYq&#xA;7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7&#xA;FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F&#xA;XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq//2Q==</xmpGImg:image>
</rdf:li>
</rdf:Alt>
</xmp:Thumbnails>
<pdf:Producer>Skia/PDF m64</pdf:Producer>
<xmpTPg:NPages>1</xmpTPg:NPages>
<xmpTPg:HasVisibleTransparency>False</xmpTPg:HasVisibleTransparency>
<xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
<xmpTPg:MaxPageSize rdf:parseType="Resource">
<stDim:w>2409.000000</stDim:w>
<stDim:h>909.000000</stDim:h>
<stDim:unit>Pixels</stDim:unit>
</xmpTPg:MaxPageSize>
<xmpTPg:PlateNames>
<rdf:Seq>
<rdf:li>Cyan</rdf:li>
<rdf:li>Magenta</rdf:li>
<rdf:li>Yellow</rdf:li>
<rdf:li>Black</rdf:li>
</rdf:Seq>
</xmpTPg:PlateNames>
<xmpTPg:SwatchGroups>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Default Swatch Group</xmpG:groupName>
<xmpG:groupType>0</xmpG:groupType>
</rdf:li>
</rdf:Seq>
</xmpTPg:SwatchGroups>
<xmpMM:InstanceID>uuid:e5f59418-0be8-dd42-a564-bc1f41615750</xmpMM:InstanceID>
<xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
<xmpMM:DocumentID>uuid:c2483dfa-3a53-3149-80a7-6822614a9dee</xmpMM:DocumentID>
<dc:format>application/pdf</dc:format>
<illustrator:CreatorSubTool>Adobe Illustrator</illustrator:CreatorSubTool>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj
4 0 obj
<</Count 1/Kids[5 0 R]/Type/Pages>>
endobj
5 0 obj
<</ArtBox[152.941 154.947 2262.04 755.845]/BleedBox[0.0 0.0 2409.0 909.0]/Contents 24 0 R/CropBox[0.0 0.0 2409.0 909.0]/LastModified(D:20220226201114-08'00')/MediaBox[0 0 2409 909]/Parent 4 0 R/PieceInfo<</Illustrator 25 0 R>>/Resources<</ExtGState<</GS0 26 0 R>>/Properties<</MC0 22 0 R>>>>/Thumb 27 0 R/TrimBox[0.0 0.0 2409.0 909.0]/Type/Page>>
endobj
24 0 obj
<</Filter/FlateDecode/Length 3540>>stream
H‰ì—ÍŽ¹„ïýõU"™ü½Z|2 Ã?ÀÀ»{Øõûþ"ɪîI¼;ö° i²ø“ÌŒˆÌüð·<C3B0>Û‡¿~ ÛŸþüq»ý| ÛcK™ÿvýúå_·nÿ¾ÅMÿ~ùñöá/ÿÛ<>ÿ¹ýÌ0ð/n}ԣƴÙÈG¨£l/Ÿnú¢ÿc:j­ÛnGëeKv¤·=Æ#uÛ,<1E>?/·=v¾o9%—mOý¨ŒòÑjÛvf»ÕsÈâŽ1Òõ½¦£Ç¡Í¹¥moáH9?œÝ& ¿{ض÷xX<78>—ak´rýÞÏokç~š½ŽÞïg¯»÷Óòbx€¿olßïÆëm¡ÝWÌ—ï§õË3WLÏíËüpYÜæŸ8«»butŒûtM´ÂÄØ^ñn9d+²®6;†ž3ÚÑ
Æ—vd³k,—q©õûŠÞ<C5A0>Ò5¶£h>†8ßÕË8JäÐqN«G Yf#Ëá5ÏD“søÇÑkòM<»š<š<>¡=xÅru¯X~œGh)k†¹¾Æ<C2BE>ºûsÝÂ%Íß";
þ’Ñ=\ŽXÚ†<C39A>Ùâý-]&t‡¯<>eã±Yn[ÎÀµ·Ë[k¨Mwžß9¾Qw6öÍ`‹ÖªÇßÇ|m-2çÏÆŠÕˆMÁ»‡wNütûáö÷öÅh툱~“<þ<>ìzæ_á„<5A> Ôô<> ,©_ ¬ãÅžh¸ä<C2B8><C3A4>èÊ<19>ü“ê<ÐOÛ.ú­sŸé§ïôvío ¦Ÿ^öH?=üòÌ3ÿ›¯ò/á<Ÿn \Ü’€Ú‚^áL¹i!“P­F£`<60>Â]°?Ç|Θ¯­‰wœv|'˜']^|_ÌtƒïÌm\8îÔÝã<C39D>ù<»‡'mÈD%–ûŠ*q™vßÕGÑ_34aèöëä}Ó­_˜»¨pnülb‰ î.H æiÚÉÄeû<65>©…r¢öÿ]aDò<44>¶Ç ¾¼%XêÚHŒrdE…ÕÁ*g§ÌÏ×[­„hl_<>
`F,<2C>B|Rä.¹\ȶ%Òf†tX<>ÿú,<2C>tÑ-É<>ÕÇ­ <1E>øk'² X;
u<EFBFBD>à]Ê{#ÔpsD™¬ë‰{!“<>Á"Nr
õ
T;ïˆÁt
ekìiÒdq>Ú8¼}Ž_n+‡<>ø²øwPX´†&üà˜0ÖHÖ‰…%îÊäQ TVÝ¢“¾“qûð@^©¸àå4¡%¸¥Ì¡é]Ië=]3~å^a £ÁÝèçgÄJÆB5<42>ÎÖ0;°¯<C2AF>\É‚).<2E>qf;ÆckÈŽŸév5^©ø±s<C2B1>ˆIâàzŽk÷<6B>³ÑpQÇ8DZ%ËŠÁWx[ -6Ljz||š‚=¦ŠG^ÇúØ<C3BA>-J…†,%yÐò´kÌß<C38C><Jüš4™ªÂÃ@ËrÐÖü¿óþŠy`·«ÞžÐXåyðnÜ.OÅîâpå Ž5œX<C593>íš(诈q2ØÀSÚÊ&wÉ¥1{¼ZÂQ㈭ž~ßϘZ”ÛU¢e…_B~1µ4¤ã( ·á`ÐÎýØŽátóÒ“7FÈcªkA Xû3ޝçÈ#«d³Æà3@Ièr}<7D>V
F “Ôa½9xIéÉëØ¡ZŠaGx°¤9”v`R#þýšÀR³Šz„d79LKžùTôt¥+\7†Ò•
™¢ì VApﮡàcÄ/Ma}I*9ÂãÏ=\EH¬¼³Éæ ]¶k'©»/z‡ãCÂÀèòm%
peJ—”Tpž¢ö´<)iùº ¤ý
JjßIIíû)i{£¤í;)©ýO%Ío•´þ_Jê”äØìB*KHão éP]$™É¨Sù<>¨Tˆ)ÔUgRBÜÎ.“I¿_µj¢À—"¿$d'|í>Zò¥Êüœa½÷T1ÝËGpe>]­¬j.Ì“Œf¥¼ãÓüÏ2þkb]QÁ¾[¡[쎥1Èdî^®-…jTá.É Žà⤂}  qV®Ñå<ú¾œÛ9\Ô“©lë¨Ò9dÕ`7Ëׄô¡)) #b5O‰¨B¬i^Ì+šËtW­³ÔЄÜ(E*^5|„Àj¾<6A>å}^ÖâÃS¥í¢Ê¿z³dòQi*«Eõ><>hDƒ/:~Œj.mf>1µN[x»/T_ œéÏðD”]¡ÏþU.Ò'zÕT« <09>ªÓ•ú<E280A2>D,­WtaŸv¶kBš­žo+¥ç5ì$tóôyNdÏÌî—t5¡ë,ìO®DÖ¼óÌ8äæ­LîzÒÌgÖšš²§Áî4xy ìêw¡fÞKé“VÄÙyrsqè}øÆ®/8ªw*^"H[JùY+ÁmÈœc¸g³=[3@TË!#aâÍC[¬B˜½œ÷‰KER™?ý¾F¸<46>:fr<66><63>ÂOÎ*ϼté±ÍÖ¶™·¶
©‡ƒd2sôpƒý«7óÚ(†I÷<49>½Þ8«9¶)) ¦5Ï„8_³ÔBC¢0;Œb,˜"ir}ØfŽ~´IÙˆã7%­ý!i_”´ü,iù­¤åß«¤…º$­M
_#Q“ ~­¨¥w5%ûwÓ´þžšÖÞ_ÓâïUÓâ¯Ô´þŽÖ…SÒ<53>«'I£³Ä e5-I•vOŽß£0ý¦çJA
%/HÞ“J˜NÙ£fÞÝÐáuN^;¢4•«¯ŽÙOY<‡ÞD9~ð dU¸·žüPþ[Xç™ÙUN<55>I¹†IÎŸÏ éü¨m•°o'L"RÏ Ž ·¨Nѧ~N<4E>¡;nÛÃÅÁôWXBÓ¶Æ™÷Ѫvo±‹ÊøÀ’ïÿôú„ÉYŒŸ <08>XÖî#ìÕI²&x™š"EZò>ŸÁ£A˜Y[Þ I59¸$ÕiŸº0uo¸RÖðǾþ(õI:DÐ{Le<4C><dÇõ¼<1F>м!xÝA=Î<16>àDg5.¯I®Hy?×ðA.Ò ¸(fŒ©å³‚ªæÚàrJ£·²dCÅL
L¿²¢†qJgbI<>cWœÝ%d Éd³zªì:ü;ö¶8¶sæÒ¸ÃbS¢—ܰèg‡êÈ?Ý‘],g'&Sò˜©>Î ]Fw°$ݵLßWÊÖK³ûÒgÚ¤ÈX`ÓÁ«ID"ÃEJ'õÓquô[Ê+¶<>PÛIΙäB³’%ìkoýä¢ÉÀê<C380>ã¿8Ø<ch¦Q}ñ
ÍGáARýÀÛ<C380>Á WªEì†ÊT3ˆÓhó”c2uõL_dd÷<64>½ÍqQ‰A,Zð¤©|žìšyõ¸ôu[Ô-ØÕ”ˆ¹ÍñÃRóŽ”ÏÐèíêlcñØ%2ìÊžå…mó±E÷ºÐôV­H3Êžé,g~R­<52>†#¼Z<C2BC>Ù>OîÅ!:;ÈP„n
I æ&øzszV7¡ „»¢3¦”ÈXŽªÅY>F=:ûê˜ecô ¬ÔV^Òilc=Þ$<24>^É$¯Kl•RjþdI€ÇäNÉôòL‰JY˜š¤TYÀÅÝqL<71>U¥Žaæzê¢HŽ$ô­ú(D/TÛ¯± î¿d—=rÛ0F{ŸÂÐ  YgRú*â"irÿ"ï-@HŠ{R$±ûí÷ã„6§<04>©á¯g3[ý­~¢»i<<3C>o¯ÀƒÎfû5Ë2<C38B>66¢M‘³)t+
b÷ó¸ÚbJRT8kÝÊÛÚ®éÝb£Ø¦©«vAaÀùuôÞiVË&1Vµé9cD<63>åJÞ {µ<>5pÁÇk‡Uâ:/~¬fŠàh½¸k6y]gÓó¯™eM†~«=<Ç0>ñcí̵{4 UoÞüýßqz?5[ø§.í ²užIà&)<0F>ýTù¹ êTçR¹8µ/oð{í4†aý?ÁÏå8FŒRÏ<52>ù>0%Ý'ó<>2pÿÞJ{€3J/nV†˜æšr7P?H™öA«ßÝNœº»Pþ¹ª‡Ïîm´€Ž<E282AC>"Fàr<72>§ÇOØdš«e»겆Aš…¾Öf=*V;J<}ç]˘ä8x3ÊXá¼E®œÔÀÊ:G8ûhÐñ`×¼©&gT<ÕŸÖV7öÙ…}pËÛµH-Z3?þy~ØÖÆ51¨EjWeœ÷œ<C3B7>ìY63Ó»YSn£Ö³ä·i3ï¼?Bþ›:[@Å9L7¡u ´ÖÝ
̵^CA†I|T“0Êçk'Çœ&ã*Tü)þß_ϳËs<C38B>æÒójoNÙ℃Íá
ú.¸(ç¤ê<C2A4>åy(Å@™7mÚ̘ʶPuÚÚªÚ­œ”P°¶<05>åJçUÒÙÃgéˆ^À’}æÉt9jËjHÉ5t]î!:>H|H>@‰êDë8p€s:¿â‰ÜlSÀ¬Ì
 ÷¨/^6“p=ëR [<1E>B°Ú ëIžf<C5BE>ŽÝ-Љµ+‡®ªÆ\q²9EÂÊ$NÅ“ƒ:=á
¸Çy§Ñ±œÝVö<EFBFBD>$8 (fóÉ~†/ç{²'<RdN87¸.O9ƒÛºû¶ÅWÜvås˜ÙvÁÖüS ²^ÚxŸÔùóëÇ'ÿþ 0B¸|
endstream
endobj
27 0 obj
<</BitsPerComponent 8/ColorSpace 28 0 R/Filter[/ASCII85Decode/FlateDecode]/Height 39/Length 181/Width 105>>stream
8;Z]!\Ij?G$j8@t/k5dUU3<am*`9`037'CAm[R>[,!)9)1.3+''h?`o>gmB[ET8ck
@lE>-DRC'5>'BJ23HlQilI&Ga&7If\2VcDkpR`P&Ag+(rGsf,$]V4,Oi!oYPO?6G/
Ye+**Y]%)+Lu]7C/1+obTM<jR!k7bhp#"6_FO&2i!:ndgO8~>
endstream
endobj
28 0 obj
[/Indexed/DeviceRGB 255 29 0 R]
endobj
29 0 obj
<</Filter[/ASCII85Decode/FlateDecode]/Length 428>>stream
8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
E1r!/,*0[*9.aFIR2&b-C#s<Xl5FH@[<=!#6V)uDBXnIr.F>oRZ7Dl%MLY\.?d>Mn
6%Q2oYfNRF$$+ON<+]RUJmC0I<jlL.oXisZ;SYU[/7#<&37rclQKqeJe#,UF7Rgb1
VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j<etJICj7e7nPMb=O6S7UOH<
PO7r\I.Hu&e0d&E<.')fERr/l+*W,)q^D*ai5<uuLX.7g/>$XKrcYp0n+Xl_nU*O(
l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~>
endstream
endobj
22 0 obj
<</Intent 30 0 R/Name(Layer 1)/Type/OCG/Usage 31 0 R>>
endobj
30 0 obj
[/View/Design]
endobj
31 0 obj
<</CreatorInfo<</Creator(Adobe Illustrator 26.0)/Subtype/Artwork>>>>
endobj
26 0 obj
<</AIS false/BM/Normal/CA 1.0/OP false/OPM 1/SA true/SMask/None/Type/ExtGState/ca 1.0/op false>>
endobj
25 0 obj
<</LastModified(D:20220226201114-08'00')/Private 32 0 R>>
endobj
32 0 obj
<</AIMetaData 33 0 R/AIPDFPrivateData1 34 0 R/ContainerVersion 12/CreatorVersion 26/RoundtripStreamType 2/RoundtripVersion 26>>
endobj
33 0 obj
<</Length 1444>>stream
%!PS-Adobe-3.0
%%Creator: Adobe Illustrator(R) 24.0
%%AI8_CreatorVersion: 26.0.3
%%For: (Michael Shamoon) ()
%%Title: (White logo - no background.pdf)
%%CreationDate: 2/26/22 8:11 PM
%%Canvassize: 16383
%%BoundingBox: 152 154 2263 756
%%HiResBoundingBox: 152.941359391029 154.946950299891 2262.04187549133 755.845102922764
%%DocumentProcessColors: Cyan Magenta Yellow Black
%AI5_FileFormat 14.0
%AI12_BuildNumber: 778
%AI3_ColorUsage: Color
%AI7_ImageSettings: 0
%%RGBProcessColor: 0 0 0 ([Registration])
%AI3_Cropmarks: 0 0 2409 909
%AI3_TemplateBox: 1203.5 454.5 1203.5 454.5
%AI3_TileBox: 826.5 166.5 1560.5 742.5
%AI3_DocumentPreview: None
%AI5_ArtSize: 14400 14400
%AI5_RulerUnits: 6
%AI24_LargeCanvasScale: 1
%AI9_ColorModel: 1
%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0
%AI5_TargetResolution: 800
%AI5_NumLayers: 1
%AI17_Begin_Content_if_version_gt:24 4
%AI10_OpenToVie: -2651 3020 0.25059563884769 0 7787.44597860344 8164.54751330906 2548 1389 18 0 0 6 45 0 0 0 1 1 0 1 1 0 1
%AI17_Alternate_Content
%AI9_OpenToView: -2651 3020 0.25059563884769 2548 1389 18 0 0 6 45 0 0 0 1 1 0 1 1 0 1
%AI17_End_Versioned_Content
%AI5_OpenViewLayers: 7
%AI17_Begin_Content_if_version_gt:24 4
%AI17_Alternate_Content
%AI17_End_Versioned_Content
%%PageOrigin:704 -46
%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142
%AI9_Flatten: 1
%AI12_CMSettings: 00.MS
%%EndComments
endstream
endobj
34 0 obj
<</Length 33953>>stream
%AI24_ZStandard_Data(µ/ýX<ž¿EZ-<2D>D¤™æ£Ù¥¶[ã*DÞJï§ënYÉwzOê°@Yþ=
)œàr<C3A0>²
}€h>@>@$F"ôãr<C3A3>.)™<>
Kˆ<C38B>j…Ãq
E`ð è‡YXZª>Tü!ò¹T Õ¸A×À”Ÿ<0E>Qìt€h4îRØdBFs2Ê<32>š<EFBFBD>´µdˆ:-¢Hæd”
â€àd”ÙÅ<18>ŠKÑ™‡c4(Vš¦X‰b'S6 
“y8p+cÁa3á.±€_&ÔAõ!êl&\âeÑñáÀºPÀêpM´ ,0O´ˆbǵ@U»Œˆ:‰•Pm<>2ŸƒMK¢€Dv´X$+Q@ƒI±H"H¦”( Š
£°:­Ïš€@ìHE;ٕΤ¡€G*ŠˆŒÃf4À±ŒÒÙŒv¤À*<01>X—N«³Ù„É,¤€¸¤¤Èô¡ð0!i™],
 ±A"˜ˆ"+2‰b'ƒÒpâ`"KDÂŽ
H*2‰@´A—ÖÀ à>)¹É® DË¢d¦€x!Ù)“`Ñp°/&‘¥³ ±TuY”•f µHÊñhiùÀRAl²ËDÔÙp@#j@!âUÂ5€…¥eD‰AK†¨³É®—¡(„t6ê°‰ø‰0Àˆ:­<C2AD>ÎFÃ@s"Rél"² %Z%„¨\2xT2 )‰<>]«ÑÆAe<41>±z„ <0B>
c“ñ’‘Àép° m…“]Bl<42>F*‹„,¢ÓÙhˆX$š] ¢Ø!e'£ÌðÅÎAFgb© ²kJÉ“e>¤•AFg£±±9HP<48>}è0eVˆ
ê%ÔÙì«XVo (|6Ù•¡©i†ìZHX Ð0aàTÁHDà"j!mZÑ* ::T¡ÜÐ+¤·-»F^ž ÁÛha°qzò²d D + =L```Š
¢Vv<56><76>,NÁã˜B !­ÂȣİÉ.…Œ†ƒØ24 +bÀƒ ”†‡ÀB@J;ÛöÒÀɃ´à@­<˜ ³É.æhN®8¼³ÙPp >Ÿƒø8" Ž<C2A0><C5BD>:BŸƒç!SL§a£NüQ:%hćà“]Á­j= Xx(¾¤œ»Iˆ“+Íã¨ìCàÀ”<>ÆFBc<ˆ88 ¬xD'Ü'%³Ë<C2B3>¥Ñ±9Cf¡¡!"G¾ )XŒGv"» 4 O„ˆ„Æ ‚%UÊ@:½™3ht6YI'ÄÒ’]ûÁ2Ze´
|IvÒ°<19>]J6`úHqL x, hJ`8m<¢ËA
ËCÀÈ`G(¥  ”ŠuHm*J"ë)!­Àì:Ù@X8² ‘ñ YXZv˜(|ïµÙgc€!x ®d×Kv2Ê•<C38A>ÕYP•xŒü™<C3BC>´ì8ÑTÙ5NcÁb¡€³0*ͳm™B!m[F@vQ|
øä¢W +®@¡o`İi}$GsÓñ`mÙ•€ ¡!«!@ÁlZ
*Ö¶5˜²ÃdB
\
˜¼lÙ•ÀÑœô, œdÅÈ(Ó9XR…ˆf5Œbò&/Ë<C38B>)#H0Ù5 p'ZNéÙpd[JAÂ$³aÄhY¸P@±HŽ„LR »$X0HHH4JL":
‰Ñ™|¼<>‰ÕGƒ€‡<E282AC>V)"€r¢±Â¡:øÈhD\h#Š<01> G*È®d<0E>86.¶ .ÙI,T2±<Á#‰¨ÈÕ«´ÑTá+R€Hˆe$b¤¡/F<>ÅqVv…|F;ð¤hàã$A”¤<E2809D>þX¤X2Ç⸠I(³ c[6
^%4p1”!!-Ò 4+ž]Ò9`%T¬€tÉg°6 ¬MƒÂydq\v]t(”pTL Ù@ù ÙH² %úŽÁƒƒŒNÁɦjS´"¨áhNNlidó`1l[vYtÅÄA…Š@”'ŒlaÈ.
ˆŽPŽ„tLa€O€C†rd£%SàN^¶ìZ©
XX<Y쀬-»V<C2BB>´ñP>-ídca
=-»VÞFCJ
L‰°°AV†£9iÒ<>l ,†mcU œ„¼¬ÕÙe€u6/°‚] <20>l+o£á¥xNž@½m¬Š
'V%m Ù?Œô䃢-<02> ΀ÍSÒ­E,<¤•H7 Tüa†¢a“]ï´ Yu "P<Z `;(w6+Pˆ<50>É£ÀqU ’…ì
eóÀñ=X´\„pJ,`”ÞÈcñbqñqض<C398>f2ÊÎ †CŸ&"RrB"ãaBÂ…å<E280A6>AQ´¬€0­çUœ<>Œ‡÷¼ @Œô‚]IE,X$]¬Žòè(Áð`FD)Ÿ-$5a<35>&wAC@c|2NFÙ2á<32><C3A1>BiãJpx x޽8<C2BD> °²qe >òŒ<$r‰@AùKh™)€±á
©Y2Eàqp2NNüÐâ!Ò%Œdp"-=¼Ä-Ä<02>ª€Y<E282AC>]šÆSïAPÒæ„@%Aòá0hÑ@8¨ + P´È Y Ââ<lh"D'qä,Žcygã0q2J<32>SJÅ3á™Ë‚Ê&#Y)œˆ ø`¸ðd×XŒŠ‡<15><>ÅËzH+Âce2;!™"5lŒXD1Ñêp`
Ðk±á$·V6
'9(?ñÎ&»0ͤ*i ‡[À®V´U CêTaJ°VJ;™HèÓ
<EFBFBD>22ÃiEúDè§”S+¥™$L)ÕGâÓ"¡¨¶’'UR'Õ©EÚh¥T¡ÆByáxyj±4ОP¬T)%‘Á²©ÐÚ<-ª-ô°l$MkUJeÑZj[)©R*HhÒ&z¤
M©N
õRO
…:R(-/+Z´i)Mtú¨P<š
©E"hÒ&ÚÕP§ŽçKʨX­H[Çê´•XÈ¥ J±¤^0Ö)E…i¬ EJ<45>R‹„­”^XXé„Z­HXëE5N˜V"¡V˜ª…™4-…½¨ØTX K<>H**©SëdÒ°”
KP+ìE5€Æby`.ÊH0 ÓP§,EÂTR'H,,¥­ca˜¶Ò´<C382>„i,m¥#9À°”
3i,„Jh*¤ (£UÂíô<C3AD>¢
h±H!ˆM…ÊÓÖ±qªØH¦©Åae¬Jx´µy>ÊbÙH*ÚF‡I”¡*áùd<EU@ÓP%Hc½h(”ª”:m(ͤj1­VÕb¡Z¬Õ
Õbm«•Ém+Õß­Â{Qß'Bƒm°2´”ZJºÒR¹­•:¹Ò¾˜X+•[ ´R¥R$Ò©•"¹U´Öi«[ ”zIq¬Í´­ Z±6ŠªÀê„*¹UZZ'É™¶’6ÕÊê„Jq¦­•ÒN,%
Åm¨ÒŠÊ¦r¦­”R9±4“CQ@QµR§MK<4D>P)šÉ±´¨¬ËÖ©…¢*pJ ÅÚT)©“¦BZ¥IÓP( ű´¨¤T­¥m«U‰¤¢¥kEZµ¨JŠr¬Õ¦™H+,ÇZÀm«
…”Ò@<40>Z¹"¡6M´"€ÓP¤jUb9ÓVêèªVІê4RËÉm«Õú«ZT)Û
er­T
J@C9ÖŠQ^X®•JÙ\JŠÊm«Õ¦ pÚ@Ø(˜IK<49>R4Pf#¥N(º±[À±V,jP)-<2D>åX«MÅ­ ZqÛjUÚKʪµi¤T%·
ÀŠ”2<EFBFBD>FP­ph)<E280BA>ÒpÊÀ°\ OéÂT)) Õ"¡PV'JcmšÚA¡H(”ª¤’"mš
iåPTK•:aª_jGr`¨Jx°U Ï
µ:¡>Ê"<22>PìÔJyÆ‘<01>q82"0Õo±H3i¦MUÒ68h¬Æby4
©µ©Vˆà´J'PcP<63>T´
€†Zµ¨JØê„*¡H(˜
[j+uÚV&€jµÂÒ,°(áÑÖ˪õÁX+Dµ¼œLš<05> k;…¾þ¯Tñµ©Åqb<71>>R´ŠÝÛ­Çרs×ýR•4Žh¦”
¥}ž2Ä০»å†*©Ó
ë”"}hœZ$”¶®€åJ•<4A>i*¤©m-ê%$áaÉŒR*œ6ÕÈ7E«à@¡´w¤hºoS´ŠÕ…Ò>ÚÒX))¤–;톘»<CB9C>
K)Ru©¤NÐN°SZ+.¡o´R´ŠÖ)åÑJÑ*Fè%Ò
#]%åÑJ¤PkEå´á­˜ŠzCl**db:m¢¿Ì¢ÚZ,mSÙ´´<E280B9>©Z/LÂÃÂ"µ¨V¬
¥*µ;­LBL&Òê1 3<>6l€RJÛH+VŠ´µ°(áQÁZ/**Ųp{<7B>tB°S+EzÚ
€ÖÂ´Ô ÕzQa%JJujP/-'¶i+Ò

ÓZ$”ŠuÂrÃÛT(U‰„Eg÷'€“„§uB<75>PH­h˜&À¦±´-S•ðxHÚæY©¶RX­­¢EÕj©P2õ©gª©»Ò2<C392>ðÃÝ·®0B—ùZ+*'me€´Òö¢âÐJ'” …¥¥T)“ÆRR¡ô„¢ql$X`m$X`-CªÅz)™°Rª%¤ ¤¡V-$Ö‚ ,­õrÚZ-RJÛL¨ÓJ‰<4A>ÕÒ84
é´‘PRd"µ´–Õ­ø‚" <09>ÒLª”fR¥P-0d4“ê¢Â„ÃÀ`ÒZ%
¥q°$2Ixxp0
J¢h)Ú“L/ªF¦Vh…«€!JLQ˜Ùðdd0eÙH<03>P+Ö ”ÁZ) Îó?a|3b©µ<C2A9>.2˜´µ84“ªtJyp0<EFBFBD><EFBFBD>¤BimžŒIv%`šM©N­K•ÁL²kCÕ<43>Ò6Z©“*EÒ68X@Ú6Ï)¥:µJ§Œc “ÞÞëTq•2®K (Z«¥¡H¦T¢<E28093>8ØÅÀ¹L ˜bzm€`y™°$!€Y`©
`‰M`y
‚ååÁÓÇêõ½5£K,><3E>зß<C39F>
»ŸëoG<EFBFBD>ÿ\bþbÇþ׺û]^X^$½0°ÔP«çJÀ´ÒÓ<>´P/¤
D s-`)¨Rœ‹¥¡hq´6OÌLÅ2W1Í\šÌeJE8LÃY)½¨:0ŽfÓL¶i*¤ÉSPp­H+˜)õ¢Zµ¨>ZëôÂ<C3B4>mš)uJYi2@ZZŠ„Êh¥N¤Š<C2A4>”"¡4N,SJCA`+,”SÅJ©EǦj<C2A6>L˜§VI[±6­tZY6•
´6Okó¬´H)ŒTJ¥ê@©TØ
ëeƒÀ6­Õ"•´k
U:µ6RF ë„R¡´ À±R¡4R-*Ee0
¦m$TH³:\Ó¨¤N%ÒÖ<C392>e0M«¤@ÁÀLšNªUÒ>2—*%%±zQm¢ÔIå•a/.•Ô©•:m)Õ…Éꥥq¨¨Pª-#Ë.V+¥µ´¨>¦TD].Óâc²)­Õ"¡BÛ
]ßǸ17ª&tÛ7<C39B>6É·Öù ©“ÊiÃÐTHÙ@;}¸I31¥" µH©Mk¡<&¯Þ؃ŠEÈŠÁöÅ^ÆdVÿv×O!ÖÍùàGïvÿ±Äâû†<C3BB>ŸGO_—Xn2¦þV[ÐNªŽ”¶Â0I•ðdWc½ -LRN&­”ÖJ©>&©“ŠaòÊÚ´H-/°õ´<C2B1>¦²:”„
µ6<EFBFBD>‰£4“Ê"Jx²K±
E*qh*©Â¤W“][`µN)<29>MC½ldvs´6<C2B4>R]` ÐN<68>¹ËçO1wo!ê䮽1wœ«µH¨UÖ3b1{Y,©e¥Ú4$)Õ餢mÈ$¶!ÚȲ+±JêÔjiš
©EÂä%<Ù…-h*$–‡ Kx² þc †ÒòraB<61>^ LV%<еH-*¤¡N!—ðdK‰„À ´ŒÔJi*$ ,Eàƒ-°6Jõa±´jËh¥´
”eW¿ß<EFBFBD>ê
<EFBFBD>ûcÌÛj§Û·i„1nz|]»bqS#GgíèXÚÆi”e×óÇøíb튭„JêdJ½¨ ²ìbÈO±M'$@ŠôɲË"S]€Ø.ç-g±­E¥T'MŠiåL©Õ¦ÂR"¡`ª¦€©TT(Lk<4C>V
Õ:udÙ¥É`š”JÕBZáÀPi¥t²È` *”e»to±…”êÄÚ´(áɲ+]/¦ÙÈlªik±^P( [ØT-’¶}²ìÂÒ±†¢2y820YÙ•±¡4"2* ’…QDJVd ¤<ž“Š&#%dìiPYp4^ÖÃÒá”a<>l8³À)ÉdDØ øD€€~ (Zf—·´à‡ED(¤<>Ð'äã$¤Ñ$ÞÙ´Š…+‰w6™!<21>Mxg“v$Þ1(øH†‡MLˆÈ8PûD¼ '³«€t²,R²'+""%ïl^ CÄöK±áÞÙ¨èïl4$(<28>Mà ðΆóÑí€Ò;€¶;¸$„“Ù¥ÞÙd†I$âTðÂáQ<C3A1>"$"­ £å
B#ÍBdŒ¨"¥”áÉ.‡‚Ø`xpÐl‡w6
­°™Œ’=4JªÞÙ@ ‡r4¼³Ñf„E”4¼³qå<>ÂHó¡`¤ÎÔ*"Í®„ ”Œ „HU>+…ö% Ù¥PÚTlH­¨¡X9HÐä„|#±+‰¼gôÀ9'
\êÄ$eQÒÀɃgÙ(
æ<EFBFBD><# (Q'U!ÂPÂ"2XbˆÁÄâ< ˜>/»@6D$ŸˆD ‰€^>ÙëŒ "LšÝ8h8y6T²÷Ð9܆*P6º-¨,8Ùo”ôeÃâäIÈtlx41a€é=-*%Œñ¶Á<19><4E>È[<17>Š*°<h<¨´ÓÊ  2$<n
Võp¡"ºàh\<$ Ëê!»T¤¤K˃{HyOL<4F>¨4m ©L)†+ã`¯¼:ð`<+AQUÃJÅé§B”(,U‰8©z‰Ð¯XV[œ`8ôæAä*Dª¹È®K,àT ˜qT”Â;…ME0•Dhf¡ãB@ 6ÓMJ8'ä" ¸<C3AC>sRa”ÐàœÜP-'<27>…¥eÆ„¤%)b€§{Ta…E²FÊ"CpÂ10m0x˜3.=ïl|#šÈg™0„æƒñ˜Yœ„,>ž¡£A}´<»º@‹“.
[2)©ÅÉ̃Š4KZ%<25><EFBFBD>{-,™ÅÉš0¦dQØÄ¢%,I€„…“
,mb
'U+;[ÂI%€ÂI<C382>DŠ&Ç…Ô¤”f·CAB8iuh¡ìÁ¨'4ŸT<8©ádÛœŒ’@Ag³éðΆY8,8L ƒƒà‘ÁÉF'7ŒŠe5Di8q ´EepÒdÀ
Ov=dpòãáÀº!âäKÉjF@ !ÁÀ”NBTò•4”(†G…¡€9…OgÓ<67>] úpNkÁ#¥+2 —ƒ<14>V@K—É‚NvTTtÅ¡Ê01 mº"%FÞÁ±HÀT¬\0 &“L#È`bf!%]:­Õ‹ J+…²­¸h("((+#¬% x ÙBDR!šKÈJ<08>ˆ2bÒÙ¬j$¤b1Ê<C38A><E28093>-#¤£ÊxaÉ XÉè¸|
XZ†HɆQbÑÙ”Œ”„d¤>20(ÑJvµ´Šhd$„dÌÈÈ‚`5ó²/B ¶Ž
â ¢ð…ÒË–]"*[âJL"<1F>. ðFa@Ãा, Ƴ*Ã;ɈB<71>b ˆ˜ØÚõIGN D\&·L“. <09>Mr\g$B7£„ÎF¡3¡{ÐÙhš“#ƒÑ‹†€ˆ\y
+b€!`y°V\ˆ‡ƒ€Í¦ºèŒãZà4lÛGÛlÖ…ÄÉ<C384>„ήìZølMv1H 0ô¶aLBÙURÀ£Ã3°á°ðÙ4
±<C2B1>æôɸ<>H(OÅbò<{@â„f;(Ð( ²+¡À‰‡´¼Æ‹Â¦¢-%“õ°D2ž àppD Øxéð‡–ìÊ®ìú`áäÁ &“°mÌÄ"cb<63>¦Ã Gh0Yò€Y¸ Á„œ+m0,<2C>
¦â€£€áˆQÀ,sÚ „+m0ÙõàJL¹Ð`^‹„"¤íJëXx>X¬;„P6ª
•lptEB<>”Gñ°<C3B1>d(2sàò±Ê®ìÊ®ì:€xÈ®UÈÆÁÇà Ç%õÀqBŠÈ®ìÊ.6™À”
C*`46V. ˜Ì.J<>ò\ P%!ÕTKå
WtÕ€²Q‰ Gçmxt<78>)è‹ b?
ŸìÚh˜ <20>ñÛ–]Ù•±à°m <0F>Î(»v@vµxFíZÙ•]$­<>è`¡%ÂÀB;<Rë@ E?% Íãá<C3A3> ÎvXˆR‰H³ ³4ˆD PÇD•±Þ<>FR,ŸÏŠcðÒÙ D<>L†"»NZÄÂÃFÅ<02>I¨eBÔ1è08lbvm°L0<(IË$4@€s <1D>]+
Z]ÙuÁ:<3A> 
±N- Çãò §ŒQÄHɧÓ]& ¸D,<|HTYv‰öckÐQyaL<>|²Ë´QC'ˆ¡ÖÆyÀÈ ½€
JPPŽ<EFBFBD>B²E0¬:R.NÞÈ3`…ôJUgÂ7 Ð…FÉó<C389>«ÏEÄ€ˆMñ¡"<n ¼Œˆ³Kãå`E%0édjeÁa±TW,#šÅ `É‘Sf9&!NF ˜xt2
fåm4¬¼<C2AC>†ÍÊ€I$Â<1D>H#—_Á¼pxX¶¨1d×6à£Ã³"rÚD°'ÃÑ qĤtBŠ@}x<>`e<>ÒØ4dhiÂlÙFª<17>ƒ<EFBFBD>‡Í tú<¸„F"T²iÈpÀ(<28>6eá”TCÊg;Ùp “•“ÒfWsŒDèÊV8..ä àa óÂá™x$@™ žì:q…H20 R ‘†
t@am°4R@
êðdW… Á(Á$´ñN"0-“<>DÅǃöÁJ÷ hÊâ`X:L*@Ló mæÂ•6šŒÈ3a2IÀ„µÁ<dD6˜Žf7@ #šÅ¨Ú€'N #<11>÷ŒN¢€ƒÉf׿
…‘/<2F>@ AÃ;Þpivé…w6Ri(5€(± '5'=#Õ \xgƒ¡¢! Ÿ „(»”Â;
Ϭè˜<,'ŸÑÇw6
õHPŠMC‡DóNn2JSfÕ¡¸øpà‰Þ,$œ\ äÄIpŸ”$Á<ì<>*¤³ÙŒ6.w6­€v0j™xgã2-Nf:CË-Ä;ÒŠ<>Óá IÀŒ^6˜Ž&#r¡¡@0iÉÁ<C389>
qvq¼† <0B>„çá=.
@:©XpX ½ 8¡(çäY-NFR„Av2àŒ†¨³qïl„ÂÓ :XY
5jyˆe•'³k31¡ÎãAPGp2J<0E>€Š5ÐÅÎA<C38E>ƒ”Ê<C38A>¤ ò:<78>Í(äáÀ(.â<>
´ØˆP«8xˆ@,ÊX>$Íx AU4@RçÁÂITv2JWAᤄ€
1eV<EFBFBD>@,GÃÁ*| <0C>44¡ '£t(œÌ®g`JRBÊ wxg³a*N %M ˆ<>Ëê)O‡gEñ+ŸÏg¡Áã¥g#„“$€(ŒÐ[ù¤hJ4ÈFƒ~t#ÂI<C382>+Ðl<C390>Ku80H'Y&É*
ds#ƒ“®"ƒ“/
œLpn>±E%ÁÉ(×CÄÉìjµÄÞ¸ACÄIN…ˆGÃÁ(„D%†àd”šQveÅCÉ u*6áAÀBI„",,d2¦Ì.Lw8©Òé±úpà(<#<23><>ˆ
Ç9÷Iyg“0 )#ïlXXZf䜌“Q&˜€@lH[áxHÃÁRИ„FZ
H*!1Ê„^²ЦdÀ
«BÕâ<C395>­,ºÒRÀtP'
¦Ä€£€É`<60><>¥³ÁL <0C>2<18> /!REZˆ<5A>°<œ4¤¼ÇåKëa­@,™]--ÒÒá!Å£áðPRЦä†1@RÉiÃ(H„X¡ÍH„0¨x…ˆmt°âÏ vƒÇÉA<C389>;yÙ@©Ì”-.<2E>ŒÅ&n>J"šìbX)ÈlCq1sñRK<E280BA>Ç
G+J;¨àH±dÕGÁÂ+ àl„<˜
ˆÍJ©ÁÓªð„D@-VCO*TCÃ@³ÙµzH±à€hØ0Ë„?`H¤
G°^¸  –”*Ã;“V<E2809C>YGGȃIY¼`€@
 `ÄÈJ(‚Ì' d,+"ŽŒ Å 2@2ŒŠÉÇCJ¼aŒL'…ƒÖn[k£9™<39>&/ÛGJ_6ÕkãDlÛ¦±8Éì:á“lñ†I¦€””$Æ
Ij<D2ó¡0 _J>ÕÊ<C395>LYayäÇI¢##[p䊆ÂH•”Š4¹<34>¨H…¨!ÊŠ“*”
ŠP.(ˆHN<48>ˆˆT±œ:6Ù²Á `]: O<>‡Y`<z€1ØŒd˜<64>”ѱáÕ3! Åy8mt:N8*<!ëA°øð<07>£°àdB>­T³k´y,<ŒÄò°±% OJ&ºx'UîÄ"[5t^V^V<D8¼÷IQpŸ”ä|4¸ÌB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#17541F;}
</style>
<path d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944c0-9.3-1.7-16.2-5.1-20.7
c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2c-4.2-5.4-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2968.8,996.6z
"/>
</g>
<path d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st0" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#17541F;}
</style>
<path d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944c0-9.3-1.7-16.2-5.1-20.7
c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2c-4.2-5.4-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2968.8,996.6z
"/>
</g>
<path d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st0" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#17541F;}
</style>
<rect class="st0" width="3212.8" height="1212.8"/>
<path d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7c-8.4-2.1-15.7-3.1-22-3.1
c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944c0-9.3-1.7-16.2-5.1-20.7
c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2c-4.2-5.4-6.6-11.9-7.1-19.6
h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9v-24.7
c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3c-4-7.3-6-15.5-6-24.6
s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8h19.4v107.8
C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16c-2.6-4.8-6.1-8.5-10.5-11.1
c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3
c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35L2968.8,996.6z
"/>
</g>
<path d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st1" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 3212.8 1212.8" style="enable-background:new 0 0 3212.8 1212.8;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#17541F;}
</style>
<path class="st0" d="M1180.9,847.9v-20.6c-18,20-43.1,30.1-75.4,30.1c-22.4,0-42.8-5.8-61-17.5c-18.3-11.7-32.5-27.8-42.9-48.3
c-10.3-20.5-15.5-43.3-15.5-68.4c0-25.1,5.2-48,15.5-68.5s24.6-36.6,42.9-48.3s38.6-17.5,61-17.5c32.3,0,57.5,10,75.4,30.1v-20.6
h85.3V848L1180.9,847.9L1180.9,847.9z M1184.4,723.1c0-17.4-5.2-31.9-15.5-43.8c-10.3-11.8-23.9-17.7-40.6-17.7
c-16.8,0-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8c10.2,11.8,23.6,17.7,40.4,17.7
s30.3-5.9,40.6-17.7C1179.3,755.1,1184.4,740.5,1184.4,723.1z"/>
<path class="st0" d="M1543.1,606.4c18.3,11.7,32.5,27.8,42.9,48.3c10.3,20.5,15.5,43.3,15.5,68.5c0,25.1-5.2,48-15.5,68.4
c-10.3,20.5-24.6,36.6-42.9,48.3s-38.6,17.5-61,17.5c-32.3,0-57.5-10-75.4-30.1v165.6h-85.3V598.4h85.3V619
c18-20,43.1-30.1,75.4-30.1C1504.5,588.9,1524.8,594.8,1543.1,606.4z M1514.8,723.1c0-17.4-5.1-31.9-15.3-43.8
c-10.2-11.8-23.6-17.7-40.4-17.7s-30.2,5.9-40.4,17.7c-10.2,11.8-15.3,26.4-15.3,43.8c0,17.4,5.1,31.9,15.3,43.8
c10.2,11.8,23.6,17.7,40.4,17.7s30.2-5.9,40.4-17.7C1509.7,755.1,1514.8,740.5,1514.8,723.1z"/>
<path class="st0" d="M1838.9,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68c11.8-20.5,28.1-36.7,48.7-48.5s43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C1821.1,778.3,1830.2,771.9,1838.9,763.5z
M1722.2,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11S1724.3,682.1,1722.2,694.4z"/>
<path class="st0" d="M2034.1,626.6c7.8-10.8,17.2-19,28.3-24.7s22-8.5,32.8-8.5c11.4,0,20,1.6,26,4.9l-10.8,72.7
c-8.4-2.1-15.7-3.1-22-3.1c-17.1,0-30.4,4.3-39.9,12.8c-9.6,8.5-14.4,24.2-14.4,46.9v120.3h-85.3V598.4h85.3V626.6L2034.1,626.6z"/>
<path class="st0" d="M2238.3,466.4v381.5H2153V466.4H2238.3z"/>
<path class="st0" d="M2486.1,763.5l53,49.4c-28.1,29.6-66.7,44.4-115.8,44.4c-28.1,0-53-5.8-74.5-17.5s-38.2-27.7-49.8-48
c-11.7-20.3-17.7-43.2-18-68.7c0-24.8,5.9-47.5,17.7-68s28.1-36.7,48.7-48.5c20.6-11.8,43.5-17.7,68.7-17.7
c24.8,0,47.6,6.1,68.2,18.2c20.6,12.1,37,29.5,49.1,52.3c12.1,22.7,18.2,49.1,18.2,79l-0.4,11.7h-181.8
c3.6,11.4,10.5,20.7,20.9,28.1c10.3,7.3,21.3,11,33,11c14.4,0,26.3-2.2,35.7-6.5C2468.4,778.3,2477.4,771.9,2486.1,763.5z
M2369.4,694.4h92.9c-2.1-12.3-7.5-22.1-16.2-29.4c-8.7-7.3-18.7-11-30.1-11s-21.5,3.7-30.3,11
C2377,672.3,2371.5,682.1,2369.4,694.4z"/>
<path class="st0" d="M2691.2,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.7,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1c-9.7-10.9-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9
c23.3,0,44.4,3.6,63.3,10.8c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6
C2715.8,656.9,2702.9,654.5,2691.2,654.5z"/>
<path class="st0" d="M2942.6,654.5c-9.9,0-17.1,1.1-21.5,3.4c-4.5,2.2-6.7,5.9-6.7,11s3.4,8.8,10.3,11.2c6.9,2.4,18,4.9,33.2,7.6
c20,3,37,6.7,50.9,11.2s26,12.1,36.1,22.9c10.2,10.8,15.3,25.9,15.3,45.3c0,29.9-10.9,52.4-32.8,67.6
c-21.8,15.1-50.3,22.7-85.3,22.7c-25.7,0-49.5-3.7-71.4-11c-21.8-7.3-37.4-14.7-46.7-22.2l33.7-60.6c10.2,9,23.4,15.8,39.7,20.4
c16.3,4.6,31.3,7,45.1,7c19.8,0,29.6-5.2,29.6-15.7c0-5.4-3.3-9.4-9.9-11.9c-6.6-2.5-17.2-5.2-31.9-7.9c-18.9-3.3-34.9-7.2-48-11.7
c-13.2-4.5-24.6-12.2-34.3-23.1s-14.6-26-14.6-45.1c0-27.2,9.7-48.5,29-63.7c19.3-15.3,46-22.9,80.1-22.9c23.3,0,44.4,3.6,63.3,10.8
c18.9,7.2,34,14.5,45.3,22l-32.8,58.8c-10.8-7.5-23.2-13.7-37.3-18.6C2967.1,656.9,2954.2,654.5,2942.6,654.5z"/>
<g>
<path class="st0" d="M2633.3,932.2h60.2v17.3h-60.2V932.2z"/>
<path class="st0" d="M2754.5,902.6c4.9-2,10.2-3.1,16-3.1c10.9,0,19.5,3.4,25.9,10.2s9.6,16.7,9.6,29.6v57.3h-19.6V944
c0-9.3-1.7-16.2-5.1-20.7c-3.4-4.5-9.1-6.7-17-6.7c-6.5,0-11.8,2.4-16.1,7.1c-4.3,4.8-6.4,11.5-6.4,20.2v52.6h-19.6v-94.6h19.6v9.5
C2745.5,907.6,2749.7,904.6,2754.5,902.6z"/>
<path class="st0" d="M2915.6,1041.4c-8.6,6.8-19.4,10.2-32.3,10.2c-7.9,0-15.2-1.4-21.9-4.1s-12.1-6.8-16.3-12.2
c-4.2-5.4-6.6-11.9-7.1-19.6h19.6c0.7,6.1,3.5,10.8,8.4,13.9c4.9,3.2,10.7,4.8,17.4,4.8c7,0,13.1-2,18.2-6c5.1-4,7.7-10.3,7.7-18.9
v-24.7c-3.6,3.4-8,6.2-13.3,8.2c-5.2,2.1-10.7,3.1-16.3,3.1c-8.7,0-16.6-2.1-23.7-6.4c-7.1-4.3-12.6-10-16.7-17.3
c-4-7.3-6-15.5-6-24.6s2-17.3,6-24.7s9.6-13.2,16.7-17.4c7.1-4.3,15-6.4,23.7-6.4c5.7,0,11.1,1,16.3,3.1s9.6,4.8,13.3,8.2v-8.8
h19.4v107.8C2928.5,1024.1,2924.2,1034.6,2915.6,1041.4z M2907.5,963.9c2.6-4.7,3.8-10,3.8-15.9s-1.3-11.2-3.8-16
c-2.6-4.8-6.1-8.5-10.5-11.1c-4.5-2.7-9.5-4-15.1-4c-5.8,0-10.9,1.4-15.4,4.3c-4.5,2.8-7.9,6.6-10.3,11.4
c-2.4,4.8-3.6,9.9-3.6,15.5c0,5.4,1.2,10.5,3.6,15.3c2.4,4.8,5.8,8.6,10.3,11.5s9.6,4.3,15.4,4.3c5.6,0,10.6-1.4,15.1-4.1
C2901.4,972.3,2904.9,968.6,2907.5,963.9z"/>
<path class="st0" d="M2968.8,996.6h-21.6l37.9-48l-36.4-46.6h22.6l25.7,33.3l25.8-33.3h21.6l-36.2,45.9l37.9,48.6h-22.6l-27.4-35
L2968.8,996.6z"/>
</g>
<path class="st0" d="M961.1,527.4c-11.5-18.9-27.4-33.7-47.6-44.7c-20.2-10.9-43-16.4-68.5-16.4h-90.6c-8.6,39.6-21.3,77.2-38,112.4
c-10,21-21.3,41-33.9,59.9v209.2h89.8v-135H845c25.4,0,48.3-5.5,68.5-16.4s36.1-25.8,47.6-44.7s17.3-39.5,17.3-61.9
C978.4,567.1,972.7,546.3,961.1,527.4z M872.3,624.8c-9.4,9-21.8,13.5-37,13.5l-62.8,0.4v-93.4l62.8-0.4c15.3,0,27.6,4.5,37,13.5
s14.1,20,14.1,33.2C886.4,604.8,881.7,615.9,872.3,624.8z"/>
<path class="st1" d="M290,906.9c-3.5-16.5-10.4-49.6-11.3-49.6c-147.1-88-129.7-240.3-81-327.4c10.4,109.7,204.6,185.4,91.4,319.5
c-0.9,1.7,5.2,22.6,10.4,41.8c22.6-38.3,56.6-84.4,54.8-88.8C215,462.9,650.3,436.8,740.8,226.1c40.9,203.7-20.9,518.9-370.8,599
c-1.7,0.9-63.5,109.7-66.2,110.6c0-1.7-26.1-0.9-22.6-9.6C283.1,920.8,286.5,913.9,290,906.9L290,906.9z M285.7,825.1
c44.4-51.4-7.8-139.3-39.2-168C299.6,748.4,296.1,801.5,285.7,825.1L285.7,825.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
width="900"
height="900"
id="svg3923"
sodipodi:docname="square.svg"
inkscape:export-filename="/tmp/test.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="0.92.2 2405546, 2018-03-11">
<metadata
id="metadata3929">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3927" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="3840"
inkscape:window-height="2096"
id="namedview3925"
showgrid="false"
inkscape:zoom="1.1360927"
inkscape:cx="635.07139"
inkscape:cy="606.383"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="g3921" />
<g
transform="matrix(10.638298,0,0,10.638298,106.38298,-206.38301)"
id="g3921">
<defs
id="SvgjsDefs1018" />
<g
id="SvgjsG1019"
featureKey="root"
style="fill:#ffffff" />
<g
id="SvgjsG1020"
featureKey="symbol1"
transform="matrix(0.10341565,0,0,0.10341565,-11.43874,18.048418)"
inkscape:export-filename="/tmp/test.png"
inkscape:export-xdpi="116.02285"
inkscape:export-ydpi="116.02285"
style="fill:#17541f">
<defs
id="defs3911" />
<g
id="g3915">
<path
d="M 231,798 C 227,779 219,741 218,741 49,640 69,465 125,365 c 12,126 235,213 105,367 -1,2 6,26 12,48 26,-44 65,-97 63,-102 C 145,288 645,258 749,16 c 47,234 -24,596 -426,688 -2,1 -73,126 -76,127 0,-2 -30,-1 -26,-11 2,-6 6,-14 10,-22 z M 330,625 C 267,476 452,312 544,271 356,439 324,564 330,625 Z m -104,79 c 51,-59 -9,-160 -45,-193 61,105 57,166 45,193 z"
style="fill:#17541f"
id="path3913"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -5,14 +5,14 @@
<trans-unit id="ngb.alert.close" datatype="html">
<source>Close</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/alert/alert.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/alert/alert.ts</context>
<context context-type="linenumber">50</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.carousel.slide-number" datatype="html">
<source> Slide <x id="INTERPOLATION" equiv-text="ueryList&lt;NgbSli"/> of <x id="INTERPOLATION_1" equiv-text="EventSource = N"/> </source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/carousel/carousel.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">131,135</context>
</context-group>
<note priority="1" from="description">Currently selected slide number read by screen reader</note>
@@ -20,114 +20,114 @@
<trans-unit id="ngb.carousel.previous" datatype="html">
<source>Previous</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/carousel/carousel.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">159,162</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.carousel.next" datatype="html">
<source>Next</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/carousel/carousel.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/carousel/carousel.ts</context>
<context context-type="linenumber">202,203</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.select-month" datatype="html">
<source>Select month</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="linenumber">91</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="linenumber">91</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.select-year" datatype="html">
<source>Select year</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="linenumber">91</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation-select.ts</context>
<context context-type="linenumber">91</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.previous-month" datatype="html">
<source>Previous month</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">83,85</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.datepicker.next-month" datatype="html">
<source>Next month</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">112</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/datepicker/datepicker-navigation.ts</context>
<context context-type="linenumber">112</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.first" datatype="html">
<source>««</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.previous" datatype="html">
<source>«</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.next" datatype="html">
<source>»</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.last" datatype="html">
<source>»»</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.first-aria" datatype="html">
<source>First</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.previous-aria" datatype="html">
<source>Previous</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.next-aria" datatype="html">
<source>Next</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.pagination.last-aria" datatype="html">
<source>Last</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/pagination/pagination-config.ts</context>
<context context-type="linenumber">20</context>
</context-group>
</trans-unit>
@@ -135,105 +135,105 @@
<source><x id="INTERPOLATION" equiv-text="barConfig);
pu"/></source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/progressbar/progressbar.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/progressbar/progressbar.ts</context>
<context context-type="linenumber">41,42</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.HH" datatype="html">
<source>HH</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.hours" datatype="html">
<source>Hours</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.MM" datatype="html">
<source>MM</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.minutes" datatype="html">
<source>Minutes</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.increment-hours" datatype="html">
<source>Increment hours</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.decrement-hours" datatype="html">
<source>Decrement hours</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.increment-minutes" datatype="html">
<source>Increment minutes</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.decrement-minutes" datatype="html">
<source>Decrement minutes</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.SS" datatype="html">
<source>SS</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.seconds" datatype="html">
<source>Seconds</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.increment-seconds" datatype="html">
<source>Increment seconds</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.decrement-seconds" datatype="html">
<source>Decrement seconds</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.timepicker.PM" datatype="html">
<source><x id="INTERPOLATION"/></source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/timepicker/timepicker-config.ts</context>
<context context-type="linenumber">21</context>
</context-group>
</trans-unit>
<trans-unit id="ngb.toast.close-aria" datatype="html">
<source>Close</source>
<context-group purpose="location">
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.2.0_@angular+core@21.2.0_@angular+_fdecb2f5429dfeda6301fd300107de5b/node_modules/src/toast/toast-config.ts</context>
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@20.0.0_@angular+common@21.1.3_@angular+core@21.1.3_@angular+_1ede04b1f6b65fa8e34a28e44afe1de9/node_modules/src/toast/toast-config.ts</context>
<context context-type="linenumber">54</context>
</context-group>
</trans-unit>

View File

@@ -11,17 +11,17 @@
},
"private": true,
"dependencies": {
"@angular/cdk": "^21.2.0",
"@angular/common": "~21.2.0",
"@angular/compiler": "~21.2.0",
"@angular/core": "~21.2.0",
"@angular/forms": "~21.2.0",
"@angular/localize": "~21.2.0",
"@angular/platform-browser": "~21.2.0",
"@angular/platform-browser-dynamic": "~21.2.0",
"@angular/router": "~21.2.0",
"@angular/cdk": "^21.1.3",
"@angular/common": "~21.1.3",
"@angular/compiler": "~21.1.3",
"@angular/core": "~21.1.3",
"@angular/forms": "~21.1.3",
"@angular/localize": "~21.1.3",
"@angular/platform-browser": "~21.1.3",
"@angular/platform-browser-dynamic": "~21.1.3",
"@angular/router": "~21.1.3",
"@ng-bootstrap/ng-bootstrap": "^20.0.0",
"@ng-select/ng-select": "^21.4.1",
"@ng-select/ng-select": "^21.2.0",
"@ngneat/dirty-check-forms": "^3.0.3",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.8",
@@ -37,25 +37,25 @@
"tslib": "^2.8.1",
"utif": "^3.1.0",
"uuid": "^13.0.0",
"zone.js": "^0.16.1"
"zone.js": "^0.16.0"
},
"devDependencies": {
"@angular-builders/custom-webpack": "^21.0.3",
"@angular-builders/jest": "^21.0.3",
"@angular-devkit/core": "^21.2.0",
"@angular-devkit/schematics": "^21.2.0",
"@angular-devkit/core": "^21.1.3",
"@angular-devkit/schematics": "^21.1.3",
"@angular-eslint/builder": "21.2.0",
"@angular-eslint/eslint-plugin": "21.2.0",
"@angular-eslint/eslint-plugin-template": "21.2.0",
"@angular-eslint/schematics": "21.2.0",
"@angular-eslint/template-parser": "21.2.0",
"@angular/build": "^21.2.0",
"@angular/cli": "~21.2.0",
"@angular/compiler-cli": "~21.2.0",
"@angular/build": "^21.1.3",
"@angular/cli": "~21.1.3",
"@angular/compiler-cli": "~21.1.3",
"@codecov/webpack-plugin": "^1.9.1",
"@playwright/test": "^1.58.2",
"@types/jest": "^30.0.0",
"@types/node": "^25.3.3",
"@types/node": "^25.2.1",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@typescript-eslint/utils": "^8.54.0",
@@ -63,12 +63,12 @@
"jest": "30.2.0",
"jest-environment-jsdom": "^30.2.0",
"jest-junit": "^16.0.0",
"jest-preset-angular": "^16.1.1",
"jest-preset-angular": "^16.0.0",
"jest-websocket-mock": "^2.5.0",
"prettier-plugin-organize-imports": "^4.3.0",
"ts-node": "~10.9.1",
"typescript": "^5.9.3",
"webpack": "^5.105.3"
"webpack": "^5.105.0"
},
"packageManager": "pnpm@10.17.1",
"pnpm": {

2599
src-ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,10 @@
[ngClass]="{ 'slim': slimSidebarEnabled, 'col-auto col-md-3 col-lg-2 col-xxxl-1' : !slimSidebarEnabled, 'py-3' : !customAppTitle?.length || slimSidebarEnabled, 'py-2': customAppTitle?.length }"
routerLink="/dashboard"
tourAnchor="tour.intro">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1.5em" height="1.5em" fill="currentColor">
<path d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" width="1em" height="1.5em" fill="currentColor">
<path
d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z"
transform="translate(0 0)" />
</svg>
<div class="ms-2 ms-md-3 d-inline-block" [class.d-md-none]="slimSidebarEnabled">
@if (customAppTitle?.length) {

View File

@@ -1,22 +1,22 @@
@if (customLogo) {
<img src="{{customLogo}}" [class]="getClasses()" [attr.style]="'height:'+height" />
} @else {
<svg [class]="getClasses()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2670 860" [attr.style]="'height:'+height">
<path class="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g class="text" style="fill: #000;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
<svg [class]="getClasses()" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.6" [attr.style]="'height:'+height">
<path class="leaf" d="M140,713.7c-3.4-16.4-10.3-49.1-11.2-49.1c-145.7-87.1-128.4-238-80.2-324.2C59,449,251.2,524,139.1,656.8 c-0.9,1.7,5.2,22.4,10.3,41.4c22.4-37.9,56-83.6,54.3-87.9C65.9,273.9,496.9,248.1,586.6,39.4c40.5,201.8-20.7,513.9-367.2,593.2 c-1.7,0.9-62.9,108.6-65.5,109.5c0-1.7-25.9-0.9-22.4-9.5C133.1,727.4,136.6,720.6,140,713.7L140,713.7z M135.7,632.6 c44-50.9-7.8-137.9-38.8-166.4C149.5,556.7,146,609.3,135.7,632.6L135.7,632.6z" transform="translate(0)" style="fill:#17541f"/>
<g class="text" style="fill:#000">
<path d="M1022.3,428.7c-17.8-19.9-42.7-29.8-74.7-29.8c-22.3,0-42.4,5.7-60.5,17.3c-18.1,11.6-32.3,27.5-42.5,47.8 s-15.3,42.9-15.3,67.8c0,24.9,5.1,47.5,15.3,67.8c10.3,20.3,24.4,36.2,42.5,47.8c18.1,11.5,38.3,17.3,60.5,17.3 c32,0,56.9-9.9,74.7-29.8v20.4v0.2h84.5V408.3h-84.5V428.7z M1010.5,575c-10.2,11.7-23.6,17.6-40.2,17.6s-29.9-5.9-40-17.6 s-15.1-26.1-15.1-43.3c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6c16.6,0,30,5.9,40.2,17.6s15.3,26.1,15.3,43.3 S1020.7,563.3,1010.5,575z" transform="translate(0)"/>
<path d="M1381,416.1c-18.1-11.5-38.3-17.3-60.5-17.4c-32,0-56.9,9.9-74.7,29.8v-20.4h-84.5v390.7h84.5v-164 c17.8,19.9,42.7,29.8,74.7,29.8c22.3,0,42.4-5.7,60.5-17.3s32.3-27.5,42.5-47.8c10.2-20.3,15.3-42.9,15.3-67.8s-5.1-47.5-15.3-67.8 C1413.2,443.6,1399.1,427.7,1381,416.1z M1337.9,575c-10.1,11.7-23.4,17.6-40,17.6s-29.9-5.9-40-17.6s-15.1-26.1-15.1-43.3 c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6s29.9,5.9,40,17.6s15.1,26.1,15.1,43.3S1347.9,563.3,1337.9,575z" transform="translate(0)"/>
<path d="M1672.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6c-20.4,11.7-36.5,27.7-48.2,48s-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S1692.6,428.8,1672.2,416.8z M1558.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H1558.3z" transform="translate(0)"/>
<path d="M1895.3,411.7c-11,5.6-20.3,13.7-28,24.4h-0.1v-28h-84.5v247.3h84.5V536.3c0-22.6,4.7-38.1,14.2-46.5 c9.5-8.5,22.7-12.7,39.6-12.7c6.2,0,13.5,1,21.8,3.1l10.7-72c-5.9-3.3-14.5-4.9-25.8-4.9C1917.1,403.3,1906.3,406.1,1895.3,411.7z" transform="translate(0)"/>
<rect x="1985" y="277.4" width="84.5" height="377.8" transform="translate(0)"/>
<path d="M2313.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6s-36.5,27.7-48.2,48c-11.7,20.3-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S2333.6,428.8,2313.2,416.8z M2199.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H2199.3z" transform="translate(0)"/>
<path d="M2583.6,507.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9 c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8 c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7 c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6 c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9 c34.7,0,62.9-7.4,84.5-22.4c21.7-15,32.5-37.3,32.5-66.9c0-19.3-5-34.2-15.1-44.9S2597.4,512.1,2583.6,507.7z" transform="translate(0)"/>
<path d="M2883.4,575.3c0-19.3-5-34.2-15.1-44.9s-22-18.3-35.8-22.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6 c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4 l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7 c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6 c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2 l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9c34.7,0,62.9-7.4,84.5-22.4 C2872.6,627.2,2883.4,604.9,2883.4,575.3z" transform="translate(0)"/>
<rect x="2460.7" y="738.7" width="59.6" height="17.2" transform="translate(0)"/>
<path d="M2596.5,706.4c-5.7,0-11,1-15.8,3s-9,5-12.5,8.9v-9.4h-19.4v93.6h19.4v-52c0-8.6,2.1-15.3,6.3-20c4.2-4.7,9.5-7.1,15.9-7.1 c7.8,0,13.4,2.3,16.8,6.7c3.4,4.5,5.1,11.3,5.1,20.5v52h19.4v-56.8c0-12.8-3.2-22.6-9.5-29.3 C2615.8,709.8,2607.3,706.4,2596.5,706.4z" transform="translate(0)"/>
<path d="M2733.8,717.7c-3.6-3.4-7.9-6.1-13.1-8.2s-10.6-3.1-16.2-3.1c-8.7,0-16.5,2.1-23.5,6.3s-12.5,10-16.5,17.3 c-4,7.3-6,15.4-6,24.4c0,8.9,2,17.1,6,24.3c4,7.3,9.5,13,16.5,17.2s14.9,6.3,23.5,6.3c5.6,0,11-1,16.2-3.1 c5.1-2.1,9.5-4.8,13.1-8.2v24.4c0,8.5-2.5,14.8-7.6,18.7c-5,3.9-11,5.9-18,5.9c-6.7,0-12.4-1.6-17.3-4.7c-4.8-3.1-7.6-7.7-8.3-13.8 h-19.4c0.6,7.7,2.9,14.2,7.1,19.5s9.6,9.3,16.2,12c6.6,2.7,13.8,4,21.7,4c12.8,0,23.5-3.4,32-10.1c8.6-6.7,12.8-17.1,12.8-31.1 V708.9h-19.2V717.7z M2732.2,770.1c-2.5,4.7-6,8.3-10.4,11.2c-4.4,2.7-9.4,4-14.9,4c-5.7,0-10.8-1.4-15.2-4.3s-7.8-6.7-10.2-11.4 c-2.3-4.8-3.5-9.8-3.5-15.2c0-5.5,1.1-10.6,3.5-15.3s5.8-8.5,10.2-11.3s9.5-4.2,15.2-4.2c5.5,0,10.5,1.4,14.9,4s7.9,6.3,10.4,11 s3.8,10,3.8,15.8S2734.7,765.4,2732.2,770.1z" transform="translate(0)"/>
<polygon points="2867.9,708.9 2846.5,708.9 2820.9,741.9 2795.5,708.9 2773.1,708.9 2809.1,755 2771.5,802.5 2792.9,802.5 2820.1,767.9 2847.2,802.6 2869.6,802.6 2832,754.4 " transform="translate(0)"/>
<path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/>
</g>
</svg>
}

View File

@@ -1,5 +1,5 @@
<pngx-page-header title="Dashboard" [subTitle]="subtitle" i18n-title tourAnchor="tour.dashboard">
<pngx-logo extra_classes="d-none d-md-block mt-n2" height="3.2rem"></pngx-logo>
<pngx-logo extra_classes="d-none d-md-block mt-n2" height="3.5rem"></pngx-logo>
</pngx-page-header>
<div class="row">

View File

@@ -43,7 +43,6 @@ from documents.plugins.helpers import ProgressManager
from documents.plugins.helpers import ProgressStatusOptions
from documents.signals import document_consumption_finished
from documents.signals import document_consumption_started
from documents.signals import document_updated
from documents.signals.handlers import run_workflows
from documents.templating.workflows import parse_w_workflow_placeholders
from documents.utils import copy_basic_file_stats
@@ -647,12 +646,6 @@ class ConsumerPlugin(
# This triggers things like file renaming
document.save()
if document.root_document_id:
document_updated.send(
sender=self.__class__,
document=document.root_document,
)
# Delete the file only if it was successfully consumed
self.log.debug(f"Deleting original file {self.input_doc.original_file}")
self.input_doc.original_file.unlink()
@@ -722,6 +715,17 @@ class ConsumerPlugin(
else None
)
version_index = (
0
if self.input_doc.root_document_id is None
else (
Document.objects.filter(
root_document_id=self.input_doc.root_document_id,
).count()
+ 1
)
)
return parse_w_workflow_placeholders(
title,
correspondent_name,
@@ -730,6 +734,8 @@ class ConsumerPlugin(
local_added,
self.filename,
self.filename,
version_label=self.metadata.version_label,
version_index=version_index,
)
def _store(

View File

@@ -1,25 +1,22 @@
from django.core.management import BaseCommand
from django.db import transaction
from documents.management.commands.base import PaperlessCommand
from documents.management.commands.mixins import ProgressBarMixin
from documents.tasks import index_optimize
from documents.tasks import index_reindex
class Command(PaperlessCommand):
class Command(ProgressBarMixin, BaseCommand):
help = "Manages the document index."
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument("command", choices=["reindex", "optimize"])
self.add_argument_progress_bar_mixin(parser)
def handle(self, *args, **options):
self.handle_progress_bar_mixin(**options)
with transaction.atomic():
if options["command"] == "reindex":
index_reindex(
iter_wrapper=lambda docs: self.track(
docs,
description="Indexing documents...",
),
)
index_reindex(progress_bar_disable=self.no_progress_bar)
elif options["command"] == "optimize":
index_optimize()

View File

@@ -1,22 +1,22 @@
from typing import Any
from django.core.management import BaseCommand
from django.db import transaction
from documents.management.commands.base import PaperlessCommand
from documents.management.commands.mixins import ProgressBarMixin
from documents.tasks import llmindex_index
class Command(PaperlessCommand):
class Command(ProgressBarMixin, BaseCommand):
help = "Manages the LLM-based vector index for Paperless."
def add_arguments(self, parser: Any) -> None:
super().add_arguments(parser)
def add_arguments(self, parser):
parser.add_argument("command", choices=["rebuild", "update"])
self.add_argument_progress_bar_mixin(parser)
def handle(self, *args: Any, **options: Any) -> None:
llmindex_index(
rebuild=options["command"] == "rebuild",
scheduled=False,
iter_wrapper=lambda docs: self.track(
docs,
description="Indexing documents...",
),
)
def handle(self, *args, **options):
self.handle_progress_bar_mixin(**options)
with transaction.atomic():
llmindex_index(
progress_bar_disable=self.no_progress_bar,
rebuild=options["command"] == "rebuild",
scheduled=False,
)

View File

@@ -1,117 +1,17 @@
"""Management command to check the document archive for issues."""
from django.core.management.base import BaseCommand
from __future__ import annotations
import logging
from typing import Any
from rich.panel import Panel
from rich.table import Table
from rich.text import Text
from documents.management.commands.base import PaperlessCommand
from documents.models import Document
from documents.sanity_checker import SanityCheckMessages
from documents.management.commands.mixins import ProgressBarMixin
from documents.sanity_checker import check_sanity
_LEVEL_STYLE: dict[int, tuple[str, str]] = {
logging.ERROR: ("bold red", "ERROR"),
logging.WARNING: ("yellow", "WARN"),
logging.INFO: ("dim", "INFO"),
}
class Command(PaperlessCommand):
class Command(ProgressBarMixin, BaseCommand):
help = "This command checks your document archive for issues."
def _render_results(self, messages: SanityCheckMessages) -> None:
"""Render sanity check results as a Rich table."""
def add_arguments(self, parser):
self.add_argument_progress_bar_mixin(parser)
if (
not messages.has_error
and not messages.has_warning
and not messages.has_info
):
self.console.print(
Panel(
"[green]No issues detected.[/green]",
title="Sanity Check",
border_style="green",
),
)
return
def handle(self, *args, **options):
self.handle_progress_bar_mixin(**options)
messages = check_sanity(progress=self.use_progress_bar, scheduled=False)
# Build a lookup for document titles
doc_pks = [pk for pk in messages.document_pks() if pk is not None]
titles: dict[int, str] = {}
if doc_pks:
titles = dict(
Document.global_objects.filter(pk__in=doc_pks)
.only("pk", "title")
.values_list("pk", "title"),
)
table = Table(
title="Sanity Check Results",
show_lines=True,
title_style="bold",
)
table.add_column("Level", width=7, no_wrap=True)
table.add_column("Document", min_width=20)
table.add_column("Issue", ratio=1)
for doc_pk, doc_messages in messages.iter_messages():
if doc_pk is not None:
title = titles.get(doc_pk, "Unknown")
doc_label = f"#{doc_pk} {title}"
else:
doc_label = "(global)"
for msg in doc_messages:
style, label = _LEVEL_STYLE.get(
msg["level"],
("dim", "INFO"),
)
table.add_row(
Text(label, style=style),
Text(doc_label),
Text(str(msg["message"])),
)
self.console.print(table)
parts: list[str] = []
if messages.document_error_count:
parts.append(
f"{messages.document_error_count} document(s) with [bold red]errors[/bold red]",
)
if messages.document_warning_count:
parts.append(
f"{messages.document_warning_count} document(s) with [yellow]warnings[/yellow]",
)
if messages.document_info_count:
parts.append(f"{messages.document_info_count} document(s) with infos")
if messages.global_warning_count:
parts.append(
f"{messages.global_warning_count} global [yellow]warning(s)[/yellow]",
)
if parts:
if len(parts) > 1:
summary = ", ".join(parts[:-1]) + " and " + parts[-1]
else:
summary = parts[0]
self.console.print(f"\nFound {summary}.")
else:
self.console.print("\nNo issues found.")
def handle(self, *args: Any, **options: Any) -> None:
messages = check_sanity(
scheduled=False,
iter_wrapper=lambda docs: self.track(
docs,
description="Checking documents...",
),
)
self._render_results(messages)
messages.log_messages()

View File

@@ -427,6 +427,16 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
def created_date(self):
return self.created
@property
def version_index(self):
if self.root_document_id is None or self.pk is None:
return 0
return Document.objects.filter(
root_document_id=self.root_document_id,
id__lte=self.id,
).count()
def add_nested_tags(self, tags) -> None:
tag_ids = set()
for tag in tags:

View File

@@ -1,174 +1,80 @@
"""
Sanity checker for the Paperless-ngx document archive.
Verifies that all documents have valid files, correct checksums,
and consistent metadata. Reports orphaned files in the media directory.
Progress display is the caller's responsibility -- pass an ``iter_wrapper``
to wrap the document queryset (e.g., with a progress bar). The default
is an identity function that adds no overhead.
"""
from __future__ import annotations
import hashlib
import logging
import uuid
from collections import defaultdict
from collections.abc import Callable
from collections.abc import Iterable
from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING
from typing import Final
from typing import TypedDict
from typing import TypeVar
from celery import states
from django.conf import settings
from django.utils import timezone
from tqdm import tqdm
from documents.models import Document
from documents.models import PaperlessTask
from paperless.config import GeneralConfig
logger = logging.getLogger("paperless.sanity_checker")
_T = TypeVar("_T")
IterWrapper = Callable[[Iterable[_T]], Iterable[_T]]
class MessageEntry(TypedDict):
"""A single sanity check message with its severity level."""
level: int
message: str
def _identity(iterable: Iterable[_T]) -> Iterable[_T]:
"""Pass through an iterable unchanged (default iter_wrapper)."""
return iterable
class SanityCheckMessages:
"""Collects sanity check messages grouped by document primary key.
Messages are categorized as error, warning, or info. ``None`` is used
as the key for messages not associated with a specific document
(e.g., orphaned files).
"""
def __init__(self) -> None:
self._messages: dict[int | None, list[MessageEntry]] = defaultdict(list)
self.has_error: bool = False
self.has_warning: bool = False
self.has_info: bool = False
self.document_count: int = 0
self.document_error_count: int = 0
self.document_warning_count: int = 0
self.document_info_count: int = 0
self.global_warning_count: int = 0
self._messages: dict[int, list[dict]] = defaultdict(list)
self.has_error = False
self.has_warning = False
# -- Recording ----------------------------------------------------------
def error(self, doc_pk: int | None, message: str) -> None:
def error(self, doc_pk, message) -> None:
self._messages[doc_pk].append({"level": logging.ERROR, "message": message})
self.has_error = True
if doc_pk is not None:
self.document_count += 1
self.document_error_count += 1
def warning(self, doc_pk: int | None, message: str) -> None:
def warning(self, doc_pk, message) -> None:
self._messages[doc_pk].append({"level": logging.WARNING, "message": message})
self.has_warning = True
if doc_pk is not None:
self.document_count += 1
self.document_warning_count += 1
else:
# This is the only type of global message we do right now
self.global_warning_count += 1
def info(self, doc_pk: int | None, message: str) -> None:
def info(self, doc_pk, message) -> None:
self._messages[doc_pk].append({"level": logging.INFO, "message": message})
self.has_info = True
if doc_pk is not None:
self.document_count += 1
self.document_info_count += 1
# -- Iteration / query --------------------------------------------------
def document_pks(self) -> list[int | None]:
"""Return all document PKs (including None for global messages)."""
return list(self._messages.keys())
def iter_messages(self) -> Iterator[tuple[int | None, list[MessageEntry]]]:
"""Iterate over (doc_pk, messages) pairs."""
yield from self._messages.items()
def __getitem__(self, item: int | None) -> list[MessageEntry]:
return self._messages[item]
# -- Summarize Helpers --------------------------------------------------
@property
def has_global_issues(self) -> bool:
return None in self._messages
@property
def total_issue_count(self) -> int:
"""Total number of error and warning messages across all documents and global."""
return (
self.document_error_count
+ self.document_warning_count
+ self.global_warning_count
)
# -- Logging output (used by Celery task path) --------------------------
def log_messages(self) -> None:
"""Write all messages to the ``paperless.sanity_checker`` logger.
logger = logging.getLogger("paperless.sanity_checker")
This is the output path for headless / Celery execution.
Management commands use Rich rendering instead.
"""
if len(self._messages) == 0:
logger.info("Sanity checker detected no issues.")
return
else:
# Query once
all_docs = Document.global_objects.all()
doc_pks = [pk for pk in self._messages if pk is not None]
titles: dict[int, str] = {}
if doc_pks:
titles = dict(
Document.global_objects.filter(pk__in=doc_pks)
.only("pk", "title")
.values_list("pk", "title"),
)
for doc_pk in self._messages:
if doc_pk is not None:
doc = all_docs.get(pk=doc_pk)
logger.info(
f"Detected following issue(s) with document #{doc.pk},"
f" titled {doc.title}",
)
for msg in self._messages[doc_pk]:
logger.log(msg["level"], msg["message"])
for doc_pk, entries in self._messages.items():
if doc_pk is not None:
title = titles.get(doc_pk, "Unknown")
logger.info(
"Detected following issue(s) with document #%s, titled %s",
doc_pk,
title,
)
for msg in entries:
logger.log(msg["level"], msg["message"])
def __len__(self):
return len(self._messages)
def __getitem__(self, item):
return self._messages[item]
class SanityCheckFailedException(Exception):
pass
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def check_sanity(*, progress=False, scheduled=True) -> SanityCheckMessages:
paperless_task = PaperlessTask.objects.create(
task_id=uuid.uuid4(),
type=PaperlessTask.TaskType.SCHEDULED_TASK
if scheduled
else PaperlessTask.TaskType.MANUAL_TASK,
task_name=PaperlessTask.TaskName.CHECK_SANITY,
status=states.STARTED,
date_created=timezone.now(),
date_started=timezone.now(),
)
messages = SanityCheckMessages()
def _build_present_files() -> set[Path]:
"""Collect all files in MEDIA_ROOT, excluding directories and ignorable files."""
present_files = {
x.resolve()
for x in Path(settings.MEDIA_ROOT).glob("**/*")
@@ -176,178 +82,95 @@ def _build_present_files() -> set[Path]:
}
lockfile = Path(settings.MEDIA_LOCK).resolve()
present_files.discard(lockfile)
if lockfile in present_files:
present_files.remove(lockfile)
general_config = GeneralConfig()
app_logo = general_config.app_logo or settings.APP_LOGO
if app_logo:
logo_file = Path(settings.MEDIA_ROOT / Path(app_logo.lstrip("/"))).resolve()
present_files.discard(logo_file)
if logo_file in present_files:
present_files.remove(logo_file)
return present_files
def _check_thumbnail(
doc: Document,
messages: SanityCheckMessages,
present_files: set[Path],
) -> None:
"""Verify the thumbnail exists and is readable."""
thumbnail_path: Final[Path] = Path(doc.thumbnail_path).resolve()
if not thumbnail_path.exists() or not thumbnail_path.is_file():
messages.error(doc.pk, "Thumbnail of document does not exist.")
return
present_files.discard(thumbnail_path)
try:
_ = thumbnail_path.read_bytes()
except OSError as e:
messages.error(doc.pk, f"Cannot read thumbnail file of document: {e}")
def _check_original(
doc: Document,
messages: SanityCheckMessages,
present_files: set[Path],
) -> None:
"""Verify the original file exists, is readable, and has matching checksum."""
source_path: Final[Path] = Path(doc.source_path).resolve()
if not source_path.exists() or not source_path.is_file():
messages.error(doc.pk, "Original of document does not exist.")
return
present_files.discard(source_path)
try:
checksum = hashlib.md5(source_path.read_bytes()).hexdigest()
except OSError as e:
messages.error(doc.pk, f"Cannot read original file of document: {e}")
else:
if checksum != doc.checksum:
messages.error(
doc.pk,
f"Checksum mismatch. Stored: {doc.checksum}, actual: {checksum}.",
)
def _check_archive(
doc: Document,
messages: SanityCheckMessages,
present_files: set[Path],
) -> None:
"""Verify archive file consistency: checksum/filename pairing and file integrity."""
if doc.archive_checksum is not None and doc.archive_filename is None:
messages.error(
doc.pk,
"Document has an archive file checksum, but no archive filename.",
)
elif doc.archive_checksum is None and doc.archive_filename is not None:
messages.error(
doc.pk,
"Document has an archive file, but its checksum is missing.",
)
elif doc.has_archive_version:
if TYPE_CHECKING:
assert isinstance(doc.archive_path, Path)
archive_path: Final[Path] = Path(doc.archive_path).resolve()
if not archive_path.exists() or not archive_path.is_file():
messages.error(doc.pk, "Archived version of document does not exist.")
return
present_files.discard(archive_path)
try:
checksum = hashlib.md5(archive_path.read_bytes()).hexdigest()
except OSError as e:
messages.error(
doc.pk,
f"Cannot read archive file of document: {e}",
)
for doc in tqdm(Document.global_objects.all(), disable=not progress):
# Check sanity of the thumbnail
thumbnail_path: Final[Path] = Path(doc.thumbnail_path).resolve()
if not thumbnail_path.exists() or not thumbnail_path.is_file():
messages.error(doc.pk, "Thumbnail of document does not exist.")
else:
if checksum != doc.archive_checksum:
messages.error(
doc.pk,
"Checksum mismatch of archived document. "
f"Stored: {doc.archive_checksum}, actual: {checksum}.",
)
if thumbnail_path in present_files:
present_files.remove(thumbnail_path)
try:
_ = thumbnail_path.read_bytes()
except OSError as e:
messages.error(doc.pk, f"Cannot read thumbnail file of document: {e}")
# Check sanity of the original file
# TODO: extract method
source_path: Final[Path] = Path(doc.source_path).resolve()
if not source_path.exists() or not source_path.is_file():
messages.error(doc.pk, "Original of document does not exist.")
else:
if source_path in present_files:
present_files.remove(source_path)
try:
checksum = hashlib.md5(source_path.read_bytes()).hexdigest()
except OSError as e:
messages.error(doc.pk, f"Cannot read original file of document: {e}")
else:
if checksum != doc.checksum:
messages.error(
doc.pk,
"Checksum mismatch. "
f"Stored: {doc.checksum}, actual: {checksum}.",
)
def _check_content(doc: Document, messages: SanityCheckMessages) -> None:
"""Flag documents with no OCR content."""
if not doc.content:
messages.info(doc.pk, "Document contains no OCR data")
# Check sanity of the archive file.
if doc.archive_checksum is not None and doc.archive_filename is None:
messages.error(
doc.pk,
"Document has an archive file checksum, but no archive filename.",
)
elif doc.archive_checksum is None and doc.archive_filename is not None:
messages.error(
doc.pk,
"Document has an archive file, but its checksum is missing.",
)
elif doc.has_archive_version:
archive_path: Final[Path] = Path(doc.archive_path).resolve()
if not archive_path.exists() or not archive_path.is_file():
messages.error(doc.pk, "Archived version of document does not exist.")
else:
if archive_path in present_files:
present_files.remove(archive_path)
try:
checksum = hashlib.md5(archive_path.read_bytes()).hexdigest()
except OSError as e:
messages.error(
doc.pk,
f"Cannot read archive file of document : {e}",
)
else:
if checksum != doc.archive_checksum:
messages.error(
doc.pk,
"Checksum mismatch of archived document. "
f"Stored: {doc.archive_checksum}, "
f"actual: {checksum}.",
)
def _check_document(
doc: Document,
messages: SanityCheckMessages,
present_files: set[Path],
) -> None:
"""Run all checks for a single document."""
_check_thumbnail(doc, messages, present_files)
_check_original(doc, messages, present_files)
_check_archive(doc, messages, present_files)
_check_content(doc, messages)
# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------
def check_sanity(
*,
scheduled: bool = True,
iter_wrapper: IterWrapper[Document] = _identity,
) -> SanityCheckMessages:
"""Run a full sanity check on the document archive.
Args:
scheduled: Whether this is a scheduled (automatic) or manual check.
Controls the task type recorded in the database.
iter_wrapper: A callable that wraps the document iterable, e.g.,
for progress bar display. Defaults to identity (no wrapping).
Returns:
A SanityCheckMessages instance containing all detected issues.
"""
paperless_task = PaperlessTask.objects.create(
task_id=uuid.uuid4(),
type=(
PaperlessTask.TaskType.SCHEDULED_TASK
if scheduled
else PaperlessTask.TaskType.MANUAL_TASK
),
task_name=PaperlessTask.TaskName.CHECK_SANITY,
status=states.STARTED,
date_created=timezone.now(),
date_started=timezone.now(),
)
messages = SanityCheckMessages()
present_files = _build_present_files()
documents = Document.global_objects.all()
for doc in iter_wrapper(documents):
_check_document(doc, messages, present_files)
# other document checks
if not doc.content:
messages.info(doc.pk, "Document contains no OCR data")
for extra_file in present_files:
messages.warning(None, f"Orphaned file in media dir: {extra_file}")
paperless_task.status = states.SUCCESS if not messages.has_error else states.FAILURE
if messages.total_issue_count == 0:
paperless_task.result = "No issues found."
else:
parts: list[str] = []
if messages.document_error_count:
parts.append(f"{messages.document_error_count} document(s) with errors")
if messages.document_warning_count:
parts.append(f"{messages.document_warning_count} document(s) with warnings")
if messages.global_warning_count:
parts.append(f"{messages.global_warning_count} global warning(s)")
paperless_task.result = ", ".join(parts) + " found."
if messages.has_error:
paperless_task.result += " Check logs for details."
# result is concatenated messages
paperless_task.result = f"{len(messages)} issues found."
if messages.has_error:
paperless_task.result += " Check logs for details."
paperless_task.date_done = timezone.now()
paperless_task.save(update_fields=["status", "result", "date_done"])
return messages

View File

@@ -2749,6 +2749,7 @@ class WorkflowActionSerializer(serializers.ModelSerializer):
created_month_name_short="",
created_day="",
created_time="",
version_index="",
)
except (ValueError, KeyError) as e:
raise serializers.ValidationError(

View File

@@ -782,14 +782,6 @@ def run_workflows(
"""
use_overrides = overrides is not None
if isinstance(document, Document) and document.root_document_id is not None:
logger.debug(
"Skipping workflow execution for version document %s",
document.pk,
)
return None
if original_file is None:
original_file = (
document.source_path if not use_overrides else document.original_file

View File

@@ -4,13 +4,11 @@ import logging
import shutil
import uuid
import zipfile
from collections.abc import Callable
from collections.abc import Iterable
from pathlib import Path
from tempfile import TemporaryDirectory
from tempfile import mkstemp
from typing import TypeVar
import tqdm
from celery import Task
from celery import shared_task
from celery import states
@@ -68,19 +66,11 @@ from paperless_ai.indexing import llm_index_add_or_update_document
from paperless_ai.indexing import llm_index_remove_document
from paperless_ai.indexing import update_llm_index
_T = TypeVar("_T")
IterWrapper = Callable[[Iterable[_T]], Iterable[_T]]
if settings.AUDIT_LOG_ENABLED:
from auditlog.models import LogEntry
logger = logging.getLogger("paperless.tasks")
def _identity(iterable: Iterable[_T]) -> Iterable[_T]:
return iterable
@shared_task
def index_optimize() -> None:
ix = index.open_index()
@@ -88,13 +78,13 @@ def index_optimize() -> None:
writer.commit(optimize=True)
def index_reindex(*, iter_wrapper: IterWrapper[Document] = _identity) -> None:
def index_reindex(*, progress_bar_disable=False) -> None:
documents = Document.objects.all()
ix = index.open_index(recreate=True)
with AsyncWriter(ix) as writer:
for document in iter_wrapper(documents):
for document in tqdm.tqdm(documents, disable=progress_bar_disable):
index.update_document(writer, document)
@@ -237,30 +227,20 @@ def consume_file(
@shared_task
def sanity_check(*, scheduled=True, raise_on_error=True):
messages = sanity_checker.check_sanity(scheduled=scheduled)
messages.log_messages()
if not messages.has_error and not messages.has_warning and not messages.has_info:
return "No issues detected."
parts: list[str] = []
if messages.document_error_count:
parts.append(f"{messages.document_error_count} document(s) with errors")
if messages.document_warning_count:
parts.append(f"{messages.document_warning_count} document(s) with warnings")
if messages.document_info_count:
parts.append(f"{messages.document_info_count} document(s) with infos")
if messages.global_warning_count:
parts.append(f"{messages.global_warning_count} global warning(s)")
summary = ", ".join(parts) + " found."
if messages.has_error:
message = summary + " Check logs for details."
message = "Sanity check exited with errors. See log."
if raise_on_error:
raise SanityCheckFailedException(message)
return message
return summary
elif messages.has_warning:
return "Sanity check exited with warnings. See log."
elif len(messages) > 0:
return "Sanity check exited with infos. See log."
else:
return "No issues detected."
@shared_task
@@ -285,6 +265,7 @@ def bulk_update_documents(document_ids) -> None:
ai_config = AIConfig()
if ai_config.llm_index_enabled:
update_llm_index(
progress_bar_disable=True,
rebuild=False,
)
@@ -471,22 +452,13 @@ def check_scheduled_workflows() -> None:
match trigger.schedule_date_field:
case WorkflowTrigger.ScheduleDateField.ADDED:
documents = Document.objects.filter(
root_document__isnull=True,
added__lte=threshold,
)
documents = Document.objects.filter(added__lte=threshold)
case WorkflowTrigger.ScheduleDateField.CREATED:
documents = Document.objects.filter(
root_document__isnull=True,
created__lte=threshold,
)
documents = Document.objects.filter(created__lte=threshold)
case WorkflowTrigger.ScheduleDateField.MODIFIED:
documents = Document.objects.filter(
root_document__isnull=True,
modified__lte=threshold,
)
documents = Document.objects.filter(modified__lte=threshold)
case WorkflowTrigger.ScheduleDateField.CUSTOM_FIELD:
# cap earliest date to avoid massive scans
@@ -524,10 +496,7 @@ def check_scheduled_workflows() -> None:
)
]
documents = Document.objects.filter(
root_document__isnull=True,
id__in=matched_ids,
)
documents = Document.objects.filter(id__in=matched_ids)
if documents.count() > 0:
documents = prefilter_documents_by_workflowtrigger(
@@ -625,7 +594,7 @@ def update_document_parent_tags(tag: Tag, new_parent: Tag) -> None:
@shared_task
def llmindex_index(
*,
iter_wrapper: IterWrapper[Document] = _identity,
progress_bar_disable=True,
rebuild=False,
scheduled=True,
auto=False,
@@ -648,7 +617,7 @@ def llmindex_index(
try:
result = update_llm_index(
iter_wrapper=iter_wrapper,
progress_bar_disable=progress_bar_disable,
rebuild=rebuild,
)
task.status = states.SUCCESS

View File

@@ -1,3 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" {{ extra_attrs }} fill="currentColor">
<path d="M341,949.1c-6.9-20.3-20.7-61.2-21.9-61-199.6-88.9-182.5-229.8-134.3-347.5,30,137.2,268.8,148.9,146.2,336-.9,2.2,10,27.8,19.5,51.3,22.7-51.9,58.6-115.5,55.8-120.8C178,398.7,724.9,299,807.1,18.5c83,251.5,53.1,659.8-377.4,814.9-2,1.4-63.5,148.6-66.9,150.2-.2-2.1-33.2,2.9-30.1-8.7,1.6-7,4.8-16.2,8.2-25.6h0v-.2h.1ZM323.1,846.2c48.3-71.9-12.7-120.8-56.9-152.2,81.2,107.4,66.4,120.8,56.9,152.2h0Z"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 198.43 238.91" {{ extra_attrs }} fill="currentColor">
<path
d="M194.7,0C164.22,70.94,17.64,79.74,64.55,194.06c.58,1.47-10.85,17-18.47,29.9-1.76-6.45-3.81-13.48-3.52-14.07,38.11-45.14-27.26-70.65-30.78-107.58C-4.64,131.62-10.5,182.92,39,212.53c.3,0,2.64,11.14,3.81,16.71a58.55,58.55,0,0,0-2.93,6.45c-1.17,2.93,7.62,2.64,7.62,3.22.88-.29,21.7-36.93,22.28-37.23C187.67,174.72,208.48,68.6,194.7,0ZM134.61,74.75C79.5,124,70.12,160.64,71.88,178.53,53.41,134.85,107.64,86.77,134.61,74.75ZM28.2,145.11c10.55,9.67,28.14,39.28,13.19,56.57C44.91,193.77,46.08,175.89,28.2,145.11Z"
transform="translate(0 0)" />
</svg>

Before

Width:  |  Height:  |  Size: 515 B

After

Width:  |  Height:  |  Size: 679 B

View File

@@ -1,18 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2670 860" {{ extra_attrs }}>
<path class="leaf" style="fill:#005616;" d="M2227.4,821.2c-6.1-17.8-18.1-53.6-19.2-53.4-174.7-77.8-159.8-201.2-117.5-304.2,26.3,120.1,235.3,130.3,128,294.1-.7,2,8.8,24.3,17.1,44.9,19.9-45.4,51.3-101.1,48.8-105.7-199.9-357.4,278.8-444.7,350.7-690.2,72.6,220.1,46.5,577.5-330.4,713.3-1.8,1.2-55.6,130-58.5,131.4-.2-1.9-29.1,2.5-26.4-7.6,1.4-6.2,4.2-14.2,7.2-22.4h0v-.2h.2,0ZM2211.7,731.2c42.3-62.9-11.1-105.7-49.8-133.2,71,94,58.1,105.7,49.8,133.2h0Z"/>
<g class="text" style="fill: #000;">
<path class="st1" d="M654.6,393.2l-.7,137.7h-85.5V188.7h85.4c.4,11.3-.3,21.7,1.3,33.8,23.1-34.1,62.3-50,101.1-38.3,16.5,5,29.6,16.4,39.7,30,34.4,46.5,35.1,134,3.6,182.2-10.1,14.4-22.5,26.9-39,33.4-39.5,15.7-81,1.1-105.9-36.6h0ZM721,362.2c21-26.1,21-82.7-.4-108.4-13.2-15.9-36.4-16.1-49.9-.4-22.2,25.8-21.7,85.3.5,110.1,13.6,15.2,36.6,15,49.7-1.3h.1Z"/>
<path class="st1" d="M164,301l-72.8.7v126.1H3.4V98.1l159.7.5c31.3,0,58.9,13.6,79.4,36.1,30.8,37.6,30.9,91.7.6,129.6-20.1,22.8-47.6,36.5-79,36.8h-.1ZM176.8,199.8c0-20.8-15.1-35-34.7-35l-51,.2v69.5l53.6-.2c18.5,0,32-15.8,32.2-34.5h-.1Z"/>
<polygon class="st1" points="1338.2 427.8 1338 366 1412.4 365.8 1412.5 139.3 1338.1 139.1 1338.1 77.4 1498.1 77.4 1498.1 365.7 1572.3 365.9 1572.5 427.7 1338.2 427.8"/>
<path class="st1" d="M1741.8,364.3c9.1-8.6,14-18.1,17.7-30.3l68.4,13.3c-10.5,45.2-46.5,79.2-92.3,86.7-59.2,9.6-118.7-14.2-138.6-73.7-10.9-32.7-10.7-68.6.6-100.9,17.7-50.6,64.3-80.5,117.1-79.1,76.5,2,113.4,65.4,111.1,136.1h-155.4c-.7,12.5,3,25,9.7,35.9,13.2,21.3,40.9,26.9,61.5,12h.2ZM1749.4,273.1c-2.4-10.8-6.9-18-13.9-24.6-12.8-8.3-30.1-9.5-43.4-1.1-9.3,5.8-14.6,15.1-18,25.7h75.3Z"/>
<path class="st1" d="M1010.3,364.3c9.1-8.5,13.9-18.1,17.7-30.3l68.4,13.3c-10.4,45.2-46.5,79.2-92.3,86.7-59.3,9.6-118.8-14.2-138.7-73.9-10.8-32.3-10.6-67.4.2-99.3,17.3-51.2,64.2-81.8,117.6-80.4,76.6,2,113.5,65.3,111.1,136.1h-155.6c-.2,12.7,3.2,25.1,9.9,35.9,13.2,21.3,40.9,27,61.5,12h.2ZM1018,273.2c-2.4-9.4-6.3-18.5-14.2-24.4-12.3-9.1-30.4-9.4-43.3-1.3-9.3,5.9-14.4,15.1-17.9,25.6h75.4Z"/>
<path class="st1" d="M424.3,376.9c-7.1,13.6-12.5,25.7-23.2,35.5-14.3,13.3-32.6,19.3-52.3,19.4-40.4.2-75.6-23.1-73.6-65.7.9-20.1,9.7-37.2,26.5-49.2,30.5-21.8,55.8-22.4,87.8-40.6,8.1-4.6,18.2-15.3,12.4-22.2s-5-3-8-3.7h-96.3v-61.8h109.6c14.7.6,28.1,2.2,41.7,7.2,23.7,8.8,39.6,29.5,39.8,55.2l.7,90.6c0,13.5,11,23,23.7,23.9l10.1.7v61.3h-29.9c-13.1,0-25.9-3-37.3-8.6-16.9-8.2-26.9-22.2-31.6-42.2h0v.2h-.1ZM364.9,370.1c6.8,5.9,16.2,6.5,24.8,2.7,18.1-7.9,16.5-38.3,16.1-55-3.6,4.3-7.4,9-12.5,11.2l-21.1,9.3c-5.8,2.5-10.6,8-11.8,13s-1,13.8,4.7,18.7h-.2Z"/>
<path class="st1" d="M1943,430.1c-33.5-8.9-68.5-33.6-78.9-68.9l66.6-27.2c11.8,22.1,31.6,42.1,57.2,39.8,4.3-.4,9.3-3.1,11.2-6,7.8-12.5-4.3-24.3-16.2-30.7l-47.3-25.2c-32.2-17.1-57.7-50.7-41.6-87.4,11.9-27,48.1-35,75.3-36h99.2v61.8h-88.6c-2.5.4-6.2,2.3-7,4.2s.7,7,2.7,8.2c31.6,18.6,88.3,38.3,103.8,72,10.4,22.6,6.7,50-9.2,69.1-29.5,35.7-86.1,36.9-127,26.1v.2h-.2,0Z"/>
<path class="st1" d="M1318.2,264.3l-68.5.2c-19.4,0-30.1,10.8-31.6,30.2v133.1h-85.7v-239h85.6l1,58.9,11.9-25.1c14.3-30.5,56.9-36.5,87.4-33.6v75.4h-.1Z"/>
<path class="st1" d="M2232.8,374.2c-26,1.2-44.6-18.4-56.5-40.1l-66.5,27.3c10.8,35.9,46.2,60.4,80.3,69.2h0c10.6,2.6,22,4.5,33.7,5.2,3.2-7.9,6.8-15.6,10.8-23.4,18.5-35.9,44.3-68.4,73.8-98.8-23.6-21.1-62.6-36.7-87-50.6-2.2-1.2-3.6-6.7-2.7-8.7.9-2,4.5-3.5,7.4-3.9h88.2v-61.8h-97.4c-27,.7-63.8,8.2-76.5,34.8-8.3,17.5-6.8,38.5,3.5,54.9,9.3,14.9,22.2,25.8,37.7,33.9l45.8,24.3c11.5,6.1,24.7,17,17.9,30.5-2.1,4.1-7.4,6.5-12.6,7.2h.1Z"/>
<path class="st1" d="M1547.6,801.6h81.2c11.6-.2,23.2-3.8,31.9-11.2,7.3-6.2,11.7-15.4,13.9-24.8l16.8-72.7c-7.2,9-12.8,16.9-20.7,24.2-18.3,16.8-42.3,23.8-66.9,19.5-32.5-5.7-46.7-34.7-47-65.6-.5-44,18.9-93.6,57.6-117.1,18-10.9,39.5-13.9,60-9.6,12.4,2.6,22.1,9.9,29.1,20,5.8,8.4,7.8,17.2,10.8,27.8l10.7-45.4,15.6.3-50.6,219.5c-2.9,12.6-8.9,24.6-18.4,32.9-12,10.4-28.1,15.1-44,15.2l-82.9.2,2.7-13.1h.2ZM1691.8,673.5c12.9-26.3,20.1-60.3,11-88.6-5.1-15.8-17.9-26.5-34.2-28.8-20.7-2.9-40.3,2.9-55.9,16.8-13.6,12.1-23.5,26.7-30.3,43.7-9.8,24.4-14.8,56.5-4.6,81.1,5,12.1,14.7,21.3,27.6,24.7,39,10.3,70.1-16,86.4-49h0Z"/>
<path class="st1" d="M1441.6,556.8c-43.6-8.7-84.4,29.7-93.8,70l-24.8,106.6h-15.7l43.1-186.4,15.6-.2-8.6,39.5c22.3-28.9,53.9-49.3,90.7-42.5,16.8,3.1,29.1,15.6,32.1,32.4,2.1,11.6,1.6,23.4-1.1,35.3l-28.1,122.2h-15.6c0,0,27.5-119.9,27.5-119.9,4.7-20.6,5.9-51.3-21.2-56.7v-.3Z"/>
<path class="st1" d="M1958.9,733.3h-16.2l-38.2-90.1-79.8,90.3-19.3-.2,77.6-87.2c5.1-5.7,11-10.1,17.2-14.5-4.6-4.7-8.5-9.6-11.3-15.3l-33.9-69.3,16.2-.2,35.3,74.1,69-73.9c6.6-.3,12.7-.3,19.6.2l-63.1,66.6c-6.4,6.8-13.4,12.5-20.9,18,3.4,3.4,7.5,7.5,9.6,12.4l38.3,89.2h-.1Z"/>
<path class="st1" d="M1224.4,635.4H3.4c1.1-5.6,1.9-9.5,3.1-13.9h1220.9l-2.9,13.9h0Z"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2897.4 896.6" {{ extra_attrs }}>
<path class="leaf" d="M140,713.7c-3.4-16.4-10.3-49.1-11.2-49.1c-145.7-87.1-128.4-238-80.2-324.2C59,449,251.2,524,139.1,656.8 c-0.9,1.7,5.2,22.4,10.3,41.4c22.4-37.9,56-83.6,54.3-87.9C65.9,273.9,496.9,248.1,586.6,39.4c40.5,201.8-20.7,513.9-367.2,593.2 c-1.7,0.9-62.9,108.6-65.5,109.5c0-1.7-25.9-0.9-22.4-9.5C133.1,727.4,136.6,720.6,140,713.7L140,713.7z M135.7,632.6 c44-50.9-7.8-137.9-38.8-166.4C149.5,556.7,146,609.3,135.7,632.6L135.7,632.6z" transform="translate(0)" style="fill:#17541f"/>
<g class="text" style="fill:#000">
<path d="M1022.3,428.7c-17.8-19.9-42.7-29.8-74.7-29.8c-22.3,0-42.4,5.7-60.5,17.3c-18.1,11.6-32.3,27.5-42.5,47.8 s-15.3,42.9-15.3,67.8c0,24.9,5.1,47.5,15.3,67.8c10.3,20.3,24.4,36.2,42.5,47.8c18.1,11.5,38.3,17.3,60.5,17.3 c32,0,56.9-9.9,74.7-29.8v20.4v0.2h84.5V408.3h-84.5V428.7z M1010.5,575c-10.2,11.7-23.6,17.6-40.2,17.6s-29.9-5.9-40-17.6 s-15.1-26.1-15.1-43.3c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6c16.6,0,30,5.9,40.2,17.6s15.3,26.1,15.3,43.3 S1020.7,563.3,1010.5,575z" transform="translate(0)"/>
<path d="M1381,416.1c-18.1-11.5-38.3-17.3-60.5-17.4c-32,0-56.9,9.9-74.7,29.8v-20.4h-84.5v390.7h84.5v-164 c17.8,19.9,42.7,29.8,74.7,29.8c22.3,0,42.4-5.7,60.5-17.3s32.3-27.5,42.5-47.8c10.2-20.3,15.3-42.9,15.3-67.8s-5.1-47.5-15.3-67.8 C1413.2,443.6,1399.1,427.7,1381,416.1z M1337.9,575c-10.1,11.7-23.4,17.6-40,17.6s-29.9-5.9-40-17.6s-15.1-26.1-15.1-43.3 c0-17.1,5-31.6,15.1-43.3s23.4-17.6,40-17.6s29.9,5.9,40,17.6s15.1,26.1,15.1,43.3S1347.9,563.3,1337.9,575z" transform="translate(0)"/>
<path d="M1672.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6c-20.4,11.7-36.5,27.7-48.2,48s-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S1692.6,428.8,1672.2,416.8z M1558.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H1558.3z" transform="translate(0)"/>
<path d="M1895.3,411.7c-11,5.6-20.3,13.7-28,24.4h-0.1v-28h-84.5v247.3h84.5V536.3c0-22.6,4.7-38.1,14.2-46.5 c9.5-8.5,22.7-12.7,39.6-12.7c6.2,0,13.5,1,21.8,3.1l10.7-72c-5.9-3.3-14.5-4.9-25.8-4.9C1917.1,403.3,1906.3,406.1,1895.3,411.7z" transform="translate(0)"/>
<rect x="1985" y="277.4" width="84.5" height="377.8" transform="translate(0)"/>
<path d="M2313.2,416.8c-20.5-12-43-18-67.6-18c-24.9,0-47.6,5.9-68,17.6s-36.5,27.7-48.2,48c-11.7,20.3-17.6,42.7-17.6,67.3 c0.3,25.2,6.2,47.8,17.8,68c11.5,20.2,28,36,49.3,47.6c21.3,11.5,45.9,17.3,73.8,17.3c48.6,0,86.8-14.7,114.7-44l-52.5-48.9 c-8.6,8.3-17.6,14.6-26.7,19c-9.3,4.3-21.1,6.4-35.3,6.4c-11.6,0-22.5-3.6-32.7-10.9c-10.3-7.3-17.1-16.5-20.7-27.8h180l0.4-11.6 c0-29.6-6-55.7-18-78.2S2333.6,428.8,2313.2,416.8z M2199.3,503.2c2.1-12.1,7.5-21.8,16.2-29.1s18.7-10.9,30-10.9 s21.2,3.6,29.8,10.9c8.6,7.2,13.9,16.9,16,29.1H2199.3z" transform="translate(0)"/>
<path d="M2583.6,507.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9 c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8 c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7 c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6 c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9 c34.7,0,62.9-7.4,84.5-22.4c21.7-15,32.5-37.3,32.5-66.9c0-19.3-5-34.2-15.1-44.9S2597.4,512.1,2583.6,507.7z" transform="translate(0)"/>
<path d="M2883.4,575.3c0-19.3-5-34.2-15.1-44.9s-22-18.3-35.8-22.7c-13.8-4.4-30.6-8.1-50.5-11.1c-15.1-2.7-26.1-5.2-32.9-7.6 c-6.8-2.4-10.2-6.1-10.2-11.1s2.3-8.7,6.7-10.9c4.4-2.2,11.5-3.3,21.3-3.3c11.6,0,24.3,2.4,38.1,7.2c13.9,4.8,26.2,11,36.9,18.4 l32.4-58.2c-11.3-7.4-26.2-14.7-44.9-21.8c-18.7-7.1-39.6-10.7-62.7-10.7c-33.7,0-60.2,7.6-79.3,22.7 c-19.1,15.1-28.7,36.1-28.7,63.1c0,19,4.8,33.9,14.4,44.7c9.6,10.8,21,18.5,34,22.9c13.1,4.5,28.9,8.3,47.6,11.6 c14.6,2.7,25.1,5.3,31.6,7.8s9.8,6.5,9.8,11.8c0,10.4-9.7,15.6-29.3,15.6c-13.7,0-28.5-2.3-44.7-6.9c-16.1-4.6-29.2-11.3-39.3-20.2 l-33.3,60c9.2,7.4,24.6,14.7,46.2,22c21.7,7.3,45.2,10.9,70.7,10.9c34.7,0,62.9-7.4,84.5-22.4 C2872.6,627.2,2883.4,604.9,2883.4,575.3z" transform="translate(0)"/>
<rect x="2460.7" y="738.7" width="59.6" height="17.2" transform="translate(0)"/>
<path d="M2596.5,706.4c-5.7,0-11,1-15.8,3s-9,5-12.5,8.9v-9.4h-19.4v93.6h19.4v-52c0-8.6,2.1-15.3,6.3-20c4.2-4.7,9.5-7.1,15.9-7.1 c7.8,0,13.4,2.3,16.8,6.7c3.4,4.5,5.1,11.3,5.1,20.5v52h19.4v-56.8c0-12.8-3.2-22.6-9.5-29.3 C2615.8,709.8,2607.3,706.4,2596.5,706.4z" transform="translate(0)"/>
<path d="M2733.8,717.7c-3.6-3.4-7.9-6.1-13.1-8.2s-10.6-3.1-16.2-3.1c-8.7,0-16.5,2.1-23.5,6.3s-12.5,10-16.5,17.3 c-4,7.3-6,15.4-6,24.4c0,8.9,2,17.1,6,24.3c4,7.3,9.5,13,16.5,17.2s14.9,6.3,23.5,6.3c5.6,0,11-1,16.2-3.1 c5.1-2.1,9.5-4.8,13.1-8.2v24.4c0,8.5-2.5,14.8-7.6,18.7c-5,3.9-11,5.9-18,5.9c-6.7,0-12.4-1.6-17.3-4.7c-4.8-3.1-7.6-7.7-8.3-13.8 h-19.4c0.6,7.7,2.9,14.2,7.1,19.5s9.6,9.3,16.2,12c6.6,2.7,13.8,4,21.7,4c12.8,0,23.5-3.4,32-10.1c8.6-6.7,12.8-17.1,12.8-31.1 V708.9h-19.2V717.7z M2732.2,770.1c-2.5,4.7-6,8.3-10.4,11.2c-4.4,2.7-9.4,4-14.9,4c-5.7,0-10.8-1.4-15.2-4.3s-7.8-6.7-10.2-11.4 c-2.3-4.8-3.5-9.8-3.5-15.2c0-5.5,1.1-10.6,3.5-15.3s5.8-8.5,10.2-11.3s9.5-4.2,15.2-4.2c5.5,0,10.5,1.4,14.9,4s7.9,6.3,10.4,11 s3.8,10,3.8,15.8S2734.7,765.4,2732.2,770.1z" transform="translate(0)"/>
<polygon points="2867.9,708.9 2846.5,708.9 2820.9,741.9 2795.5,708.9 2773.1,708.9 2809.1,755 2771.5,802.5 2792.9,802.5 2820.1,767.9 2847.2,802.6 2869.6,802.6 2832,754.4 " transform="translate(0)"/>
<path d="M757.6,293.7c-20-10.8-42.6-16.2-67.8-16.2H600c-8.5,39.2-21.1,76.4-37.6,111.3c-9.9,20.8-21.1,40.6-33.6,59.4v207.2h88.9 V521.5h72c25.2,0,47.8-5.4,67.8-16.2s35.7-25.6,47.1-44.2c11.4-18.7,17.1-39.1,17.1-61.3c0.1-22.7-5.6-43.3-17-61.9 C793.3,319.2,777.6,304.5,757.6,293.7z M716.6,434.3c-9.3,8.9-21.6,13.3-36.7,13.3l-62.2,0.4v-92.5l62.2-0.4 c15.1,0,27.3,4.4,36.7,13.3c9.4,8.9,14,19.9,14,32.9C730.6,414.5,726,425.4,716.6,434.3z" transform="translate(0)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -6,6 +6,7 @@ from pathlib import PurePath
import pathvalidate
from django.utils import timezone
from django.utils.functional import SimpleLazyObject
from django.utils.text import slugify as django_slugify
from jinja2 import StrictUndefined
from jinja2 import Template
@@ -113,6 +114,7 @@ def create_dummy_document():
archive_filename="/dummy/archive_filename.pdf",
original_filename="original_file.pdf",
archive_serial_number=12345,
version_label="Version #1",
)
return dummy_doc
@@ -155,14 +157,15 @@ def get_basic_metadata_context(
document: Document,
*,
no_value_default: str = NO_VALUE_PLACEHOLDER,
) -> dict[str, str]:
version_index: object | None = None,
) -> dict[str, object]:
"""
Given a Document, constructs some basic information about it. If certain values are not set,
they will be replaced with the no_value_default.
Regardless of set or not, the values will be sanitized
"""
return {
context: dict[str, object] = {
"title": pathvalidate.sanitize_filename(
document.title,
replacement_text="-",
@@ -189,17 +192,30 @@ def get_basic_metadata_context(
if document.original_filename
else no_value_default,
"doc_pk": f"{document.pk:07}",
"version_label": pathvalidate.sanitize_filename(
document.version_label,
replacement_text="-",
)
if document.version_label
else no_value_default,
}
if version_index is not None:
context["version_index"] = version_index
return context
def get_safe_document_context(
document: Document,
tags: Iterable[Tag],
*,
version_index: object | None = None,
) -> dict[str, object]:
"""
Build a document context object to avoid supplying entire model instance.
"""
return {
context: dict[str, object] = {
"id": document.pk,
"pk": document.pk,
"title": document.title,
@@ -237,6 +253,11 @@ def get_safe_document_context(
else None,
}
if version_index is not None:
context["version_index"] = version_index
return context
def get_tags_context(tags: Iterable[Tag]) -> dict[str, str | list[str]]:
"""
@@ -347,9 +368,20 @@ def validate_filepath_template_and_render(
custom_fields = CustomFieldInstance.global_objects.filter(document=document)
# Build the context dictionary
lazy_version_index = SimpleLazyObject(lambda: document.version_index)
context = (
{"document": get_safe_document_context(document, tags=tags_list)}
| get_basic_metadata_context(document, no_value_default=NO_VALUE_PLACEHOLDER)
{
"document": get_safe_document_context(
document,
tags=tags_list,
version_index=lazy_version_index,
),
}
| get_basic_metadata_context(
document,
no_value_default=NO_VALUE_PLACEHOLDER,
version_index=lazy_version_index,
)
| get_creation_date_context(document)
| get_added_date_context(document)
| get_tags_context(tags_list)

View File

@@ -41,6 +41,8 @@ def parse_w_workflow_placeholders(
doc_title: str | None = None,
doc_url: str | None = None,
doc_id: int | None = None,
version_label: str | None = None,
version_index: int | None = None,
) -> str:
"""
Available title placeholders for Workflows depend on what has already been assigned,
@@ -62,6 +64,8 @@ def parse_w_workflow_placeholders(
"owner_username": owner_username,
"original_filename": Path(original_filename).stem,
"filename": Path(filename).stem,
"version_label": version_label or "",
"version_index": version_index or "1",
}
if created is not None:
formatting.update(

View File

@@ -1,96 +1,10 @@
import shutil
import zoneinfo
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING
import filelock
import pytest
from django.contrib.auth import get_user_model
from pytest_django.fixtures import SettingsWrapper
from rest_framework.test import APIClient
from documents.tests.factories import DocumentFactory
if TYPE_CHECKING:
from documents.models import Document
@dataclass(frozen=True, slots=True)
class PaperlessDirs:
"""Standard Paperless-ngx directory layout for tests."""
media: Path
originals: Path
archive: Path
thumbnails: Path
@pytest.fixture(scope="session")
def samples_dir() -> Path:
"""Path to the shared test sample documents."""
return Path(__file__).parent / "samples" / "documents"
@pytest.fixture()
def paperless_dirs(tmp_path: Path) -> PaperlessDirs:
"""Create and return the directory structure for testing."""
media = tmp_path / "media"
dirs = PaperlessDirs(
media=media,
originals=media / "documents" / "originals",
archive=media / "documents" / "archive",
thumbnails=media / "documents" / "thumbnails",
)
for d in (dirs.originals, dirs.archive, dirs.thumbnails):
d.mkdir(parents=True)
return dirs
@pytest.fixture()
def _media_settings(paperless_dirs: PaperlessDirs, settings) -> None:
"""Configure Django settings to point at temp directories."""
settings.MEDIA_ROOT = paperless_dirs.media
settings.ORIGINALS_DIR = paperless_dirs.originals
settings.ARCHIVE_DIR = paperless_dirs.archive
settings.THUMBNAIL_DIR = paperless_dirs.thumbnails
settings.MEDIA_LOCK = paperless_dirs.media / "media.lock"
settings.IGNORABLE_FILES = {".DS_Store", "Thumbs.db", "desktop.ini"}
settings.APP_LOGO = ""
@pytest.fixture()
def sample_doc(
paperless_dirs: PaperlessDirs,
_media_settings: None,
samples_dir: Path,
) -> "Document":
"""Create a document with valid files and matching checksums."""
with filelock.FileLock(paperless_dirs.media / "media.lock"):
shutil.copy(
samples_dir / "originals" / "0000001.pdf",
paperless_dirs.originals / "0000001.pdf",
)
shutil.copy(
samples_dir / "archive" / "0000001.pdf",
paperless_dirs.archive / "0000001.pdf",
)
shutil.copy(
samples_dir / "thumbnails" / "0000001.webp",
paperless_dirs.thumbnails / "0000001.webp",
)
return DocumentFactory(
title="test",
checksum="42995833e01aea9b3edee44bbfdd7ce1",
archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b",
content="test content",
pk=1,
filename="0000001.pdf",
mime_type="application/pdf",
archive_filename="0000001.pdf",
)
@pytest.fixture()
def settings_timezone(settings: SettingsWrapper) -> zoneinfo.ZoneInfo:

View File

@@ -1,193 +0,0 @@
"""Tests for the document_sanity_checker management command.
Verifies Rich rendering (table, panel, summary) and end-to-end CLI behavior.
"""
from __future__ import annotations
from io import StringIO
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from django.core.management import call_command
from rich.console import Console
from documents.management.commands.document_sanity_checker import Command
from documents.sanity_checker import SanityCheckMessages
from documents.tests.factories import DocumentFactory
if TYPE_CHECKING:
from documents.models import Document
from documents.tests.conftest import PaperlessDirs
def _render_to_string(messages: SanityCheckMessages) -> str:
"""Render command output to a plain string for assertion."""
buf = StringIO()
cmd = Command()
cmd.console = Console(file=buf, width=120, no_color=True)
cmd._render_results(messages)
return buf.getvalue()
# ---------------------------------------------------------------------------
# Rich rendering
# ---------------------------------------------------------------------------
class TestRenderResultsNoIssues:
"""No DB access needed -- renders an empty SanityCheckMessages."""
def test_shows_panel(self) -> None:
output = _render_to_string(SanityCheckMessages())
assert "No issues detected" in output
assert "Sanity Check" in output
@pytest.mark.django_db
class TestRenderResultsWithIssues:
def test_error_row(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.error(sample_doc.pk, "Original missing")
output = _render_to_string(msgs)
assert "Sanity Check Results" in output
assert "ERROR" in output
assert "Original missing" in output
assert f"#{sample_doc.pk}" in output
assert sample_doc.title in output
def test_warning_row(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.warning(sample_doc.pk, "Suspicious file")
output = _render_to_string(msgs)
assert "WARN" in output
assert "Suspicious file" in output
def test_info_row(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.info(sample_doc.pk, "No OCR data")
output = _render_to_string(msgs)
assert "INFO" in output
assert "No OCR data" in output
@pytest.mark.usefixtures("_media_settings")
def test_global_message(self) -> None:
msgs = SanityCheckMessages()
msgs.warning(None, "Orphaned file: /tmp/stray.pdf")
output = _render_to_string(msgs)
assert "(global)" in output
assert "Orphaned file" in output
def test_multiple_messages_same_doc(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.error(sample_doc.pk, "Thumbnail missing")
msgs.error(sample_doc.pk, "Checksum mismatch")
output = _render_to_string(msgs)
assert "Thumbnail missing" in output
assert "Checksum mismatch" in output
@pytest.mark.usefixtures("_media_settings")
def test_unknown_doc_pk(self) -> None:
msgs = SanityCheckMessages()
msgs.error(99999, "Ghost document")
output = _render_to_string(msgs)
assert "#99999" in output
assert "Unknown" in output
@pytest.mark.django_db
class TestRenderResultsSummary:
def test_errors_only(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.error(sample_doc.pk, "broken")
output = _render_to_string(msgs)
assert "1 document(s) with" in output
assert "errors" in output
def test_warnings_only(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.warning(sample_doc.pk, "odd")
output = _render_to_string(msgs)
assert "1 document(s) with" in output
assert "warnings" in output
def test_infos_only(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.info(sample_doc.pk, "no OCR")
output = _render_to_string(msgs)
assert "1 document(s) with infos" in output
def test_empty_messages(self) -> None:
msgs = SanityCheckMessages()
output = _render_to_string(msgs)
assert "No issues detected." in output
def test_document_errors_and_global_warnings(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.error(sample_doc.pk, "broken")
msgs.warning(None, "orphan")
output = _render_to_string(msgs)
assert "1 document(s) with" in output
assert "errors" in output
assert "1 global warning(s)" in output
assert "2 document(s)" not in output
def test_global_warnings_only(self) -> None:
msgs = SanityCheckMessages()
msgs.warning(None, "extra file")
output = _render_to_string(msgs)
assert "1 global warning(s)" in output
assert "document(s) with" not in output
def test_all_levels_combined(self, sample_doc: Document) -> None:
msgs = SanityCheckMessages()
msgs.error(sample_doc.pk, "broken")
msgs.warning(sample_doc.pk, "odd")
msgs.info(sample_doc.pk, "fyi")
msgs.warning(None, "extra file")
output = _render_to_string(msgs)
assert "1 document(s) with errors" in output
assert "1 document(s) with warnings" in output
assert "1 document(s) with infos" in output
assert "1 global warning(s)" in output
# ---------------------------------------------------------------------------
# End-to-end command execution
# ---------------------------------------------------------------------------
@pytest.mark.django_db
@pytest.mark.management
class TestDocumentSanityCheckerCommand:
def test_no_issues(self, sample_doc: Document) -> None:
out = StringIO()
call_command("document_sanity_checker", "--no-progress-bar", stdout=out)
assert "No issues detected" in out.getvalue()
def test_missing_original(self, sample_doc: Document) -> None:
Path(sample_doc.source_path).unlink()
out = StringIO()
call_command("document_sanity_checker", "--no-progress-bar", stdout=out)
output = out.getvalue()
assert "ERROR" in output
assert "Original of document does not exist" in output
@pytest.mark.usefixtures("_media_settings")
def test_checksum_mismatch(self, paperless_dirs: PaperlessDirs) -> None:
"""Lightweight document with zero-byte files triggers checksum mismatch."""
doc = DocumentFactory(
title="test",
content="test",
filename="test.pdf",
checksum="abc",
)
Path(doc.source_path).touch()
Path(doc.thumbnail_path).touch()
out = StringIO()
call_command("document_sanity_checker", "--no-progress-bar", stdout=out)
output = out.getvalue()
assert "ERROR" in output
assert "Checksum mismatch. Stored: abc, actual:" in output

View File

@@ -267,7 +267,7 @@ class TestApiStoragePaths(DirectoriesMixin, APITestCase):
"/{created_month_name_short}/{created_day}/{added}/{added_year}"
"/{added_year_short}/{added_month}/{added_month_name}"
"/{added_month_name_short}/{added_day}/{asn}"
"/{tag_list}/{owner_username}/{original_name}/{doc_pk}/",
"/{tag_list}/{owner_username}/{original_name}/{doc_pk}/{version_index}/",
},
),
content_type="application/json",

View File

@@ -355,6 +355,35 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
self.assertEqual(Workflow.objects.count(), 1)
def test_api_create_assign_title_accepts_version_index(self) -> None:
response = self.client.post(
self.ENDPOINT,
json.dumps(
{
"name": "Workflow with version index",
"order": 1,
"triggers": [
{
"type": WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
},
],
"actions": [
{
"assign_title": "Version {version_index}",
},
],
},
),
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
workflow = Workflow.objects.get(name="Workflow with version index")
self.assertEqual(
workflow.actions.first().assign_title,
"Version {version_index}",
)
def test_api_create_workflow_trigger_action_empty_fields(self) -> None:
"""
GIVEN:

View File

@@ -428,7 +428,11 @@ class TestConsumer(
DocumentMetadataOverrides(
correspondent_id=c.pk,
document_type_id=dt.pk,
title="{{correspondent}}{{document_type}} {{added_month}}-{{added_year_short}}",
title=(
"{{correspondent}}{{document_type}} "
"{{added_month}}-{{added_year_short}}.{{version_label}}"
),
version_label="v2",
),
) as consumer:
consumer.run()
@@ -436,7 +440,10 @@ class TestConsumer(
document = Document.objects.first()
now = timezone.now()
self.assertEqual(document.title, f"{c.name}{dt.name} {now.strftime('%m-%y')}")
self.assertEqual(
document.title,
f"{c.name}{dt.name} {now.strftime('%m-%y')}.v2",
)
self._assert_first_last_send_progress()
def testOverrideOwner(self) -> None:

View File

@@ -156,6 +156,54 @@ class TestDocument(TestCase):
)
self.assertEqual(doc.get_public_filename(), "2020-12-25 test")
def test_version_index_for_root_is_zero(self) -> None:
root = Document.objects.create(
title="Root",
content="content",
checksum="checksum-root",
mime_type="application/pdf",
created=date(2025, 1, 1),
)
self.assertEqual(root.version_index, 0)
def test_version_index_uses_id_ordering(self) -> None:
root = Document.objects.create(
title="Root",
content="content",
checksum="checksum-root",
mime_type="application/pdf",
created=date(2025, 1, 1),
)
v1 = Document.objects.create(
root_document=root,
title="V1",
content="content",
checksum="checksum-v1",
mime_type="application/pdf",
created=date(2025, 1, 3),
)
v2 = Document.objects.create(
root_document=root,
title="V2",
content="content",
checksum="checksum-v2",
mime_type="application/pdf",
created=date(2025, 1, 2),
)
v3 = Document.objects.create(
root_document=root,
title="V3",
content="content",
checksum="checksum-v3",
mime_type="application/pdf",
created=date(2025, 1, 1),
)
self.assertEqual(v1.version_index, 1)
self.assertEqual(v2.version_index, 2)
self.assertEqual(v3.version_index, 3)
def test_suggestion_content() -> None:
"""

View File

@@ -281,6 +281,32 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(generate_filename(d1), Path("652 - the_doc.pdf"))
self.assertEqual(generate_filename(d2), Path("none - the_doc.pdf"))
@override_settings(FILENAME_FORMAT="{title}.{version_label}")
def test_version_label(self) -> None:
d1 = Document.objects.create(
title="the_doc",
mime_type="application/pdf",
checksum="A",
version_label="Version #2",
)
d2 = Document.objects.create(
title="the_doc",
mime_type="application/pdf",
checksum="B",
)
d3 = Document.objects.create(
title="the_doc",
mime_type="application/pdf",
checksum="C",
version_label="Super weird %@\"'<> ¯\\_(ツ)_/¯",
)
self.assertEqual(generate_filename(d1), Path("the_doc.Version #2.pdf"))
self.assertEqual(generate_filename(d2), Path("the_doc.none.pdf"))
self.assertEqual(
generate_filename(d3),
Path("the_doc.Super weird %@-'-- ¯-_(ツ)_-¯.pdf"),
)
@override_settings(FILENAME_FORMAT="{title} {tag_list}")
def test_tag_list(self) -> None:
doc = Document.objects.create(title="doc1", mime_type="application/pdf")
@@ -383,6 +409,21 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(generate_filename(document), Path("0013579.pdf"))
@override_settings(FILENAME_FORMAT="{version_index}")
def test_format_version_index(self) -> None:
root = Document.objects.create(
checksum="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
mime_type="application/pdf",
)
version = Document.objects.create(
root_document=root,
checksum="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
mime_type="application/pdf",
)
self.assertEqual(generate_filename(root), Path("0.pdf"))
self.assertEqual(generate_filename(version), Path("1.pdf"))
@override_settings(FILENAME_FORMAT=None)
def test_format_none(self) -> None:
document = Document()

View File

@@ -134,7 +134,6 @@ class TestRenamer(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertIsFile(doc2.archive_path)
@pytest.mark.management
class TestCreateClassifier(TestCase):
@mock.patch(
"documents.management.commands.document_create_classifier.train_classifier",
@@ -145,6 +144,32 @@ class TestCreateClassifier(TestCase):
m.assert_called_once()
@pytest.mark.management
class TestSanityChecker(DirectoriesMixin, TestCase):
def test_no_issues(self) -> None:
with self.assertLogs() as capture:
call_command("document_sanity_checker")
self.assertEqual(len(capture.output), 1)
self.assertIn("Sanity checker detected no issues.", capture.output[0])
def test_errors(self) -> None:
doc = Document.objects.create(
title="test",
content="test",
filename="test.pdf",
checksum="abc",
)
Path(doc.source_path).touch()
Path(doc.thumbnail_path).touch()
with self.assertLogs() as capture:
call_command("document_sanity_checker")
self.assertEqual(len(capture.output), 2)
self.assertIn("Checksum mismatch. Stored: abc, actual:", capture.output[1])
@pytest.mark.management
class TestConvertMariaDBUUID(TestCase):
@mock.patch("django.db.connection.schema_editor")

View File

@@ -288,7 +288,7 @@ class TestExportImport(
self.assertEqual(Permission.objects.count(), num_permission_objects)
messages = check_sanity()
# everything is alright after the test
self.assertEqual(messages.total_issue_count, 0)
self.assertEqual(len(messages), 0)
def test_exporter_with_filename_format(self) -> None:
shutil.rmtree(Path(self.dirs.media_dir) / "documents")

View File

@@ -1,295 +1,192 @@
"""Tests for the sanity checker module.
Tests exercise ``check_sanity`` as a whole, verifying document validation,
orphan detection, task recording, and the iter_wrapper contract.
"""
from __future__ import annotations
import logging
import shutil
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
import filelock
from django.conf import settings
from django.test import TestCase
from django.test import override_settings
from documents.models import Document
from documents.models import PaperlessTask
from documents.sanity_checker import check_sanity
if TYPE_CHECKING:
from collections.abc import Iterable
from documents.tests.conftest import PaperlessDirs
from documents.tests.utils import DirectoriesMixin
@pytest.mark.django_db
class TestCheckSanityNoDocuments:
"""Sanity checks against an empty archive."""
class TestSanityCheck(DirectoriesMixin, TestCase):
def make_test_data(self):
with filelock.FileLock(settings.MEDIA_LOCK):
# just make sure that the lockfile is present.
shutil.copy(
(
Path(__file__).parent
/ "samples"
/ "documents"
/ "originals"
/ "0000001.pdf"
),
Path(self.dirs.originals_dir) / "0000001.pdf",
)
shutil.copy(
(
Path(__file__).parent
/ "samples"
/ "documents"
/ "archive"
/ "0000001.pdf"
),
Path(self.dirs.archive_dir) / "0000001.pdf",
)
shutil.copy(
(
Path(__file__).parent
/ "samples"
/ "documents"
/ "thumbnails"
/ "0000001.webp"
),
Path(self.dirs.thumbnail_dir) / "0000001.webp",
)
@pytest.mark.usefixtures("_media_settings")
def test_no_documents(self) -> None:
return Document.objects.create(
title="test",
checksum="42995833e01aea9b3edee44bbfdd7ce1",
archive_checksum="62acb0bcbfbcaa62ca6ad3668e4e404b",
content="test",
pk=1,
filename="0000001.pdf",
mime_type="application/pdf",
archive_filename="0000001.pdf",
)
def assertSanityError(self, doc: Document, messageRegex) -> None:
messages = check_sanity()
assert not messages.has_error
assert not messages.has_warning
assert messages.total_issue_count == 0
@pytest.mark.usefixtures("_media_settings")
def test_no_issues_logs_clean(self, caplog: pytest.LogCaptureFixture) -> None:
messages = check_sanity()
with caplog.at_level(logging.INFO, logger="paperless.sanity_checker"):
self.assertTrue(messages.has_error)
with self.assertLogs() as capture:
messages.log_messages()
assert "Sanity checker detected no issues." in caplog.text
@pytest.mark.django_db
class TestCheckSanityHealthyDocument:
def test_no_errors(self, sample_doc: Document) -> None:
messages = check_sanity()
assert not messages.has_error
assert not messages.has_warning
assert messages.total_issue_count == 0
@pytest.mark.django_db
class TestCheckSanityThumbnail:
def test_missing(self, sample_doc: Document) -> None:
Path(sample_doc.thumbnail_path).unlink()
messages = check_sanity()
assert messages.has_error
assert any(
"Thumbnail of document does not exist" in m["message"]
for m in messages[sample_doc.pk]
)
def test_unreadable(self, sample_doc: Document) -> None:
thumb = Path(sample_doc.thumbnail_path)
thumb.chmod(0o000)
try:
messages = check_sanity()
assert messages.has_error
assert any(
"Cannot read thumbnail" in m["message"] for m in messages[sample_doc.pk]
self.assertEqual(
capture.records[0].message,
f"Detected following issue(s) with document #{doc.pk}, titled {doc.title}",
)
finally:
thumb.chmod(0o644)
self.assertRegex(capture.records[1].message, messageRegex)
@pytest.mark.django_db
class TestCheckSanityOriginal:
def test_missing(self, sample_doc: Document) -> None:
Path(sample_doc.source_path).unlink()
def test_no_issues(self) -> None:
self.make_test_data()
messages = check_sanity()
assert messages.has_error
assert any(
"Original of document does not exist" in m["message"]
for m in messages[sample_doc.pk]
)
def test_checksum_mismatch(self, sample_doc: Document) -> None:
sample_doc.checksum = "badhash"
sample_doc.save()
messages = check_sanity()
assert messages.has_error
assert any(
"Checksum mismatch" in m["message"] and "badhash" in m["message"]
for m in messages[sample_doc.pk]
)
def test_unreadable(self, sample_doc: Document) -> None:
src = Path(sample_doc.source_path)
src.chmod(0o000)
try:
messages = check_sanity()
assert messages.has_error
assert any(
"Cannot read original" in m["message"] for m in messages[sample_doc.pk]
self.assertFalse(messages.has_error)
self.assertFalse(messages.has_warning)
with self.assertLogs() as capture:
messages.log_messages()
self.assertEqual(len(capture.output), 1)
self.assertEqual(capture.records[0].levelno, logging.INFO)
self.assertEqual(
capture.records[0].message,
"Sanity checker detected no issues.",
)
finally:
src.chmod(0o644)
def test_no_docs(self) -> None:
self.assertEqual(len(check_sanity()), 0)
@pytest.mark.django_db
class TestCheckSanityArchive:
def test_checksum_without_filename(self, sample_doc: Document) -> None:
sample_doc.archive_filename = None
sample_doc.save()
def test_success(self) -> None:
self.make_test_data()
self.assertEqual(len(check_sanity()), 0)
def test_no_thumbnail(self) -> None:
doc = self.make_test_data()
Path(doc.thumbnail_path).unlink()
self.assertSanityError(doc, "Thumbnail of document does not exist")
def test_thumbnail_no_access(self) -> None:
doc = self.make_test_data()
Path(doc.thumbnail_path).chmod(0o000)
self.assertSanityError(doc, "Cannot read thumbnail file of document")
Path(doc.thumbnail_path).chmod(0o777)
def test_no_original(self) -> None:
doc = self.make_test_data()
Path(doc.source_path).unlink()
self.assertSanityError(doc, "Original of document does not exist.")
def test_original_no_access(self) -> None:
doc = self.make_test_data()
Path(doc.source_path).chmod(0o000)
self.assertSanityError(doc, "Cannot read original file of document")
Path(doc.source_path).chmod(0o777)
def test_original_checksum_mismatch(self) -> None:
doc = self.make_test_data()
doc.checksum = "WOW"
doc.save()
self.assertSanityError(doc, "Checksum mismatch. Stored: WOW, actual: ")
def test_no_archive(self) -> None:
doc = self.make_test_data()
Path(doc.archive_path).unlink()
self.assertSanityError(doc, "Archived version of document does not exist.")
def test_archive_no_access(self) -> None:
doc = self.make_test_data()
Path(doc.archive_path).chmod(0o000)
self.assertSanityError(doc, "Cannot read archive file of document")
Path(doc.archive_path).chmod(0o777)
def test_archive_checksum_mismatch(self) -> None:
doc = self.make_test_data()
doc.archive_checksum = "WOW"
doc.save()
self.assertSanityError(doc, "Checksum mismatch of archived document")
def test_empty_content(self) -> None:
doc = self.make_test_data()
doc.content = ""
doc.save()
messages = check_sanity()
assert messages.has_error
assert any(
"checksum, but no archive filename" in m["message"]
for m in messages[sample_doc.pk]
self.assertFalse(messages.has_error)
self.assertFalse(messages.has_warning)
self.assertEqual(len(messages), 1)
self.assertRegex(
messages[doc.pk][0]["message"],
"Document contains no OCR data",
)
def test_filename_without_checksum(self, sample_doc: Document) -> None:
sample_doc.archive_checksum = None
sample_doc.save()
def test_orphaned_file(self) -> None:
self.make_test_data()
Path(self.dirs.originals_dir, "orphaned").touch()
messages = check_sanity()
assert messages.has_error
assert any(
"checksum is missing" in m["message"] for m in messages[sample_doc.pk]
self.assertTrue(messages.has_warning)
self.assertRegex(
messages._messages[None][0]["message"],
"Orphaned file in media dir",
)
def test_missing_file(self, sample_doc: Document) -> None:
Path(sample_doc.archive_path).unlink()
messages = check_sanity()
assert messages.has_error
assert any(
"Archived version of document does not exist" in m["message"]
for m in messages[sample_doc.pk]
)
def test_checksum_mismatch(self, sample_doc: Document) -> None:
sample_doc.archive_checksum = "wronghash"
sample_doc.save()
messages = check_sanity()
assert messages.has_error
assert any(
"Checksum mismatch of archived document" in m["message"]
for m in messages[sample_doc.pk]
)
def test_unreadable(self, sample_doc: Document) -> None:
archive = Path(sample_doc.archive_path)
archive.chmod(0o000)
try:
messages = check_sanity()
assert messages.has_error
assert any(
"Cannot read archive" in m["message"] for m in messages[sample_doc.pk]
)
finally:
archive.chmod(0o644)
def test_no_archive_at_all(self, sample_doc: Document) -> None:
"""Document with neither archive checksum nor filename is valid."""
Path(sample_doc.archive_path).unlink()
sample_doc.archive_checksum = None
sample_doc.archive_filename = None
sample_doc.save()
messages = check_sanity()
assert not messages.has_error
@pytest.mark.django_db
class TestCheckSanityContent:
@pytest.mark.parametrize(
"content",
[
pytest.param("", id="empty-string"),
],
@override_settings(
APP_LOGO="logo/logo.png",
)
def test_no_content(self, sample_doc: Document, content: str) -> None:
sample_doc.content = content
sample_doc.save()
def test_ignore_logo(self) -> None:
self.make_test_data()
logo_dir = Path(self.dirs.media_dir, "logo")
logo_dir.mkdir(parents=True, exist_ok=True)
Path(self.dirs.media_dir, "logo", "logo.png").touch()
messages = check_sanity()
assert not messages.has_error
assert not messages.has_warning
assert any("no OCR data" in m["message"] for m in messages[sample_doc.pk])
self.assertFalse(messages.has_warning)
@pytest.mark.django_db
class TestCheckSanityOrphans:
def test_orphaned_file(
self,
sample_doc: Document,
paperless_dirs: PaperlessDirs,
) -> None:
(paperless_dirs.originals / "orphan.pdf").touch()
def test_ignore_ignorable_files(self) -> None:
self.make_test_data()
Path(self.dirs.media_dir, ".DS_Store").touch()
Path(self.dirs.media_dir, "desktop.ini").touch()
messages = check_sanity()
assert messages.has_warning
assert any("Orphaned file" in m["message"] for m in messages[None])
self.assertFalse(messages.has_warning)
@pytest.mark.usefixtures("_media_settings")
def test_ignorable_files_not_flagged(
self,
paperless_dirs: PaperlessDirs,
) -> None:
(paperless_dirs.media / ".DS_Store").touch()
(paperless_dirs.media / "desktop.ini").touch()
messages = check_sanity()
assert not messages.has_warning
def test_archive_filename_no_checksum(self) -> None:
doc = self.make_test_data()
doc.archive_checksum = None
doc.save()
self.assertSanityError(doc, "has an archive file, but its checksum is missing.")
@pytest.mark.django_db
class TestCheckSanityIterWrapper:
def test_wrapper_receives_documents(self, sample_doc: Document) -> None:
seen: list[Document] = []
def tracking(iterable: Iterable[Document]) -> Iterable[Document]:
for item in iterable:
seen.append(item)
yield item
check_sanity(iter_wrapper=tracking)
assert len(seen) == 1
assert seen[0].pk == sample_doc.pk
def test_default_works_without_wrapper(self, sample_doc: Document) -> None:
messages = check_sanity()
assert not messages.has_error
@pytest.mark.django_db
class TestCheckSanityTaskRecording:
@pytest.mark.parametrize(
("expected_type", "scheduled"),
[
pytest.param(PaperlessTask.TaskType.SCHEDULED_TASK, True, id="scheduled"),
pytest.param(PaperlessTask.TaskType.MANUAL_TASK, False, id="manual"),
],
)
@pytest.mark.usefixtures("_media_settings")
def test_task_type(self, expected_type: str, *, scheduled: bool) -> None:
check_sanity(scheduled=scheduled)
task = PaperlessTask.objects.latest("date_created")
assert task.task_name == PaperlessTask.TaskName.CHECK_SANITY
assert task.type == expected_type
def test_success_status(self, sample_doc: Document) -> None:
check_sanity()
task = PaperlessTask.objects.latest("date_created")
assert task.status == "SUCCESS"
def test_failure_status(self, sample_doc: Document) -> None:
Path(sample_doc.source_path).unlink()
check_sanity()
task = PaperlessTask.objects.latest("date_created")
assert task.status == "FAILURE"
assert "Check logs for details" in task.result
@pytest.mark.django_db
class TestCheckSanityLogMessages:
def test_logs_doc_issues(
self,
sample_doc: Document,
caplog: pytest.LogCaptureFixture,
) -> None:
Path(sample_doc.source_path).unlink()
messages = check_sanity()
with caplog.at_level(logging.INFO, logger="paperless.sanity_checker"):
messages.log_messages()
assert f"document #{sample_doc.pk}" in caplog.text
assert "Original of document does not exist" in caplog.text
def test_logs_global_issues(
self,
sample_doc: Document,
paperless_dirs: PaperlessDirs,
caplog: pytest.LogCaptureFixture,
) -> None:
(paperless_dirs.originals / "orphan.pdf").touch()
messages = check_sanity()
with caplog.at_level(logging.WARNING, logger="paperless.sanity_checker"):
messages.log_messages()
assert "Orphaned file" in caplog.text
@pytest.mark.usefixtures("_media_settings")
def test_logs_unknown_doc_pk(self, caplog: pytest.LogCaptureFixture) -> None:
"""A doc PK not in the DB logs 'Unknown' as the title."""
messages = check_sanity()
messages.error(99999, "Ghost document")
with caplog.at_level(logging.INFO, logger="paperless.sanity_checker"):
messages.log_messages()
assert "#99999" in caplog.text
assert "Unknown" in caplog.text
def test_archive_checksum_no_filename(self) -> None:
doc = self.make_test_data()
doc.archive_filename = None
doc.save()
self.assertSanityError(
doc,
"has an archive file checksum, but no archive filename.",
)

View File

@@ -3,7 +3,6 @@ from datetime import timedelta
from pathlib import Path
from unittest import mock
import pytest
from celery import states
from django.conf import settings
from django.test import TestCase
@@ -106,83 +105,55 @@ class TestClassifier(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertNotEqual(mtime2, mtime3)
@pytest.mark.django_db
class TestSanityCheck:
@pytest.fixture
def mock_check_sanity(self, mocker) -> mock.MagicMock:
return mocker.patch("documents.tasks.sanity_checker.check_sanity")
class TestSanityCheck(DirectoriesMixin, TestCase):
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_success(self, m) -> None:
m.return_value = SanityCheckMessages()
self.assertEqual(tasks.sanity_check(), "No issues detected.")
m.assert_called_once()
def test_sanity_check_success(self, mock_check_sanity: mock.MagicMock) -> None:
mock_check_sanity.return_value = SanityCheckMessages()
assert tasks.sanity_check() == "No issues detected."
mock_check_sanity.assert_called_once()
def test_sanity_check_error_raises(
self,
mock_check_sanity: mock.MagicMock,
sample_doc: Document,
) -> None:
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_error(self, m) -> None:
messages = SanityCheckMessages()
messages.error(sample_doc.pk, "some error")
mock_check_sanity.return_value = messages
with pytest.raises(SanityCheckFailedException):
tasks.sanity_check()
mock_check_sanity.assert_called_once()
messages.error(None, "Some error")
m.return_value = messages
self.assertRaises(SanityCheckFailedException, tasks.sanity_check)
m.assert_called_once()
def test_sanity_check_error_no_raise(
self,
mock_check_sanity: mock.MagicMock,
sample_doc: Document,
) -> None:
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_error_no_raise(self, m) -> None:
messages = SanityCheckMessages()
messages.error(sample_doc.pk, "some error")
mock_check_sanity.return_value = messages
messages.error(None, "Some error")
m.return_value = messages
# No exception should be raised
result = tasks.sanity_check(raise_on_error=False)
assert "1 document(s) with errors" in result
assert "Check logs for details." in result
mock_check_sanity.assert_called_once()
self.assertEqual(
result,
"Sanity check exited with errors. See log.",
)
m.assert_called_once()
def test_sanity_check_warning_only(
self,
mock_check_sanity: mock.MagicMock,
) -> None:
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_warning(self, m) -> None:
messages = SanityCheckMessages()
messages.warning(None, "extra file")
mock_check_sanity.return_value = messages
result = tasks.sanity_check()
assert result == "1 global warning(s) found."
mock_check_sanity.assert_called_once()
messages.warning(None, "Some warning")
m.return_value = messages
self.assertEqual(
tasks.sanity_check(),
"Sanity check exited with warnings. See log.",
)
m.assert_called_once()
def test_sanity_check_info_only(
self,
mock_check_sanity: mock.MagicMock,
sample_doc: Document,
) -> None:
@mock.patch("documents.tasks.sanity_checker.check_sanity")
def test_sanity_check_info(self, m) -> None:
messages = SanityCheckMessages()
messages.info(sample_doc.pk, "some info")
mock_check_sanity.return_value = messages
result = tasks.sanity_check()
assert result == "1 document(s) with infos found."
mock_check_sanity.assert_called_once()
def test_sanity_check_errors_warnings_and_infos(
self,
mock_check_sanity: mock.MagicMock,
sample_doc: Document,
) -> None:
messages = SanityCheckMessages()
messages.error(sample_doc.pk, "broken")
messages.warning(sample_doc.pk, "odd")
messages.info(sample_doc.pk, "fyi")
messages.warning(None, "extra file")
mock_check_sanity.return_value = messages
result = tasks.sanity_check(raise_on_error=False)
assert "1 document(s) with errors" in result
assert "1 document(s) with warnings" in result
assert "1 document(s) with infos" in result
assert "1 global warning(s)" in result
assert "Check logs for details." in result
mock_check_sanity.assert_called_once()
messages.info(None, "Some info")
m.return_value = messages
self.assertEqual(
tasks.sanity_check(),
"Sanity check exited with infos. See log.",
)
m.assert_called_once()
class TestBulkUpdate(DirectoriesMixin, TestCase):

View File

@@ -951,6 +951,38 @@ class TestWorkflows(
self.assertEqual(doc.correspondent, self.c2)
self.assertEqual(doc.title, f"Doc created in {created.year}")
def test_document_updated_workflow_version_index_placeholder(self) -> None:
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
assign_title="Version {{ version_index }}",
)
workflow = Workflow.objects.create(
name="Workflow version index",
order=0,
)
workflow.triggers.add(trigger)
workflow.actions.add(action)
workflow.save()
root = Document.objects.create(
title="root",
checksum="cccccccccccccccccccccccccccccccc",
mime_type="application/pdf",
)
version = Document.objects.create(
title="v1",
checksum="dddddddddddddddddddddddddddddddd",
mime_type="application/pdf",
root_document=root,
)
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, version)
version.refresh_from_db()
self.assertEqual(version.title, "Version 1")
def test_document_added_no_match_filename(self) -> None:
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
@@ -1742,48 +1774,6 @@ class TestWorkflows(
self.assertEqual(doc.title, "Doc {created_year]")
def test_document_updated_workflow_ignores_version_documents(self) -> None:
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
workflow = Workflow.objects.create(
name="Workflow 1",
order=0,
)
workflow.triggers.add(trigger)
workflow.actions.add(action)
root_doc = Document.objects.create(
title="root",
correspondent=self.c,
original_filename="root.pdf",
)
version_doc = Document.objects.create(
title="version",
correspondent=self.c,
original_filename="version.pdf",
root_document=root_doc,
)
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, version_doc)
root_doc.refresh_from_db()
version_doc.refresh_from_db()
self.assertIsNone(root_doc.owner)
self.assertIsNone(version_doc.owner)
self.assertFalse(
WorkflowRun.objects.filter(
workflow=workflow,
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
document=version_doc,
).exists(),
)
def test_document_updated_workflow(self) -> None:
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
@@ -2052,60 +2042,6 @@ class TestWorkflows(
doc.refresh_from_db()
self.assertEqual(doc.owner, self.user2)
def test_workflow_scheduled_trigger_ignores_version_documents(self) -> None:
trigger = WorkflowTrigger.objects.create(
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
schedule_offset_days=1,
schedule_date_field=WorkflowTrigger.ScheduleDateField.ADDED,
)
action = WorkflowAction.objects.create(
assign_title="Doc assign owner",
assign_owner=self.user2,
)
workflow = Workflow.objects.create(
name="Workflow 1",
order=0,
)
workflow.triggers.add(trigger)
workflow.actions.add(action)
root_doc = Document.objects.create(
title="root",
correspondent=self.c,
original_filename="root.pdf",
added=timezone.now() - timedelta(days=10),
)
version_doc = Document.objects.create(
title="version",
correspondent=self.c,
original_filename="version.pdf",
root_document=root_doc,
added=timezone.now() - timedelta(days=10),
)
tasks.check_scheduled_workflows()
root_doc.refresh_from_db()
version_doc.refresh_from_db()
self.assertEqual(root_doc.owner, self.user2)
self.assertIsNone(version_doc.owner)
self.assertEqual(
WorkflowRun.objects.filter(
workflow=workflow,
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
document=root_doc,
).count(),
1,
)
self.assertFalse(
WorkflowRun.objects.filter(
workflow=workflow,
type=WorkflowTrigger.WorkflowTriggerType.SCHEDULED,
document=version_doc,
).exists(),
)
@mock.patch("documents.models.Document.objects.filter", autospec=True)
def test_workflow_scheduled_trigger_modified(self, mock_filter) -> None:
"""
@@ -3508,7 +3444,10 @@ class TestWorkflows(
)
webhook_action = WorkflowActionWebhook.objects.create(
use_params=False,
body="Test message: {{doc_url}} with id {{doc_id}}",
body=(
"Test message: {{doc_url}} with id {{doc_id}} "
"and version {{version_label}}"
),
url="http://paperless-ngx.com",
include_document=False,
)
@@ -3532,6 +3471,7 @@ class TestWorkflows(
title="sample test",
correspondent=self.c,
original_filename="sample.pdf",
version_label="v3",
)
run_workflows(WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED, doc)
@@ -3540,7 +3480,7 @@ class TestWorkflows(
url="http://paperless-ngx.com",
data=(
f"Test message: http://localhost:8000/paperless/documents/{doc.id}/"
f" with id {doc.id}"
f" with id {doc.id} and version {doc.version_label}"
),
headers={},
files=None,

View File

@@ -1758,11 +1758,6 @@ class DocumentViewSet(
.order_by("-id")
.first()
)
document_updated.send(
sender=self.__class__,
document=root_doc,
)
return Response(
{
"result": "OK",
@@ -1832,11 +1827,6 @@ class DocumentViewSet(
},
)
document_updated.send(
sender=self.__class__,
document=root_doc,
)
return Response(
{
"id": version_doc.id,

View File

@@ -6,6 +6,7 @@ from pathlib import Path
from django.conf import settings
from django.contrib.auth.models import User
from django.utils import timezone
from django.utils.functional import SimpleLazyObject
from documents.data_models import ConsumableDocument
from documents.data_models import DocumentMetadataOverrides
@@ -24,6 +25,16 @@ from documents.workflows.webhooks import send_webhook
logger = logging.getLogger("paperless.workflows.actions")
def _get_consumable_version_index(document: ConsumableDocument) -> int:
if document.root_document_id is None:
return 0
# The document is not yet saved, so use the next index in the version chain.
return (
Document.objects.filter(root_document_id=document.root_document_id).count() + 1
)
def build_workflow_action_context(
document: Document | ConsumableDocument,
overrides: DocumentMetadataOverrides | None,
@@ -34,6 +45,11 @@ def build_workflow_action_context(
use_overrides = overrides is not None
if not use_overrides:
version_index = (
document.version_index
if isinstance(document, Document)
else _get_consumable_version_index(document)
)
return {
"title": document.title,
"doc_url": f"{settings.PAPERLESS_URL}{settings.BASE_URL}documents/{document.pk}/",
@@ -49,6 +65,8 @@ def build_workflow_action_context(
"added": timezone.localtime(document.added),
"created": document.created,
"id": document.pk,
"version_label": document.version_label,
"version_index": version_index,
}
correspondent_obj = (
@@ -68,6 +86,11 @@ def build_workflow_action_context(
)
filename = document.original_file if document.original_file else ""
version_index = (
SimpleLazyObject(lambda: _get_consumable_version_index(document))
if isinstance(document, ConsumableDocument)
else SimpleLazyObject(lambda: document.version_index)
)
return {
"title": overrides.title
if overrides and overrides.title
@@ -81,6 +104,8 @@ def build_workflow_action_context(
"added": timezone.localtime(timezone.now()),
"created": overrides.created if overrides else None,
"id": "",
"version_label": overrides.version_label if overrides else None,
"version_index": version_index,
}
@@ -116,6 +141,8 @@ def execute_email_action(
context["title"],
context["doc_url"],
context["id"],
context["version_label"],
context["version_index"],
)
if action.email.subject
else ""
@@ -133,6 +160,8 @@ def execute_email_action(
context["title"],
context["doc_url"],
context["id"],
context["version_label"],
context["version_index"],
)
if action.email.body
else ""
@@ -212,6 +241,8 @@ def execute_webhook_action(
context["title"],
context["doc_url"],
context["id"],
context["version_label"],
context["version_index"],
)
except Exception as e:
logger.error(
@@ -231,6 +262,8 @@ def execute_webhook_action(
context["title"],
context["doc_url"],
context["id"],
context["version_label"],
context["version_index"],
)
headers = {}
if action.webhook.headers:

View File

@@ -58,6 +58,8 @@ def apply_assignment_to_document(
"", # dont pass the title to avoid recursion
"", # no urls in titles
document.pk,
document.version_label,
document.version_index,
)
except Exception: # pragma: no cover
logger.exception(

View File

@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-02 16:22+0000\n"
"POT-Creation-Date: 2026-02-28 10:33+0000\n"
"PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n"
"Language-Team: English\n"
@@ -1339,7 +1339,7 @@ msgstr ""
msgid "Duplicate document identifiers are not allowed."
msgstr ""
#: documents/serialisers.py:2419 documents/views.py:3327
#: documents/serialisers.py:2419 documents/views.py:3317
#, python-format
msgid "Documents not found: %(ids)s"
msgstr ""
@@ -1603,20 +1603,20 @@ msgstr ""
msgid "Unable to parse URI {value}"
msgstr ""
#: documents/views.py:3339
#: documents/views.py:3329
#, python-format
msgid "Insufficient permissions to share document %(id)s."
msgstr ""
#: documents/views.py:3382
#: documents/views.py:3372
msgid "Bundle is already being processed."
msgstr ""
#: documents/views.py:3439
#: documents/views.py:3429
msgid "The share link bundle is still being prepared. Please try again later."
msgstr ""
#: documents/views.py:3449
#: documents/views.py:3439
msgid "The share link bundle is unavailable."
msgstr ""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -378,6 +378,7 @@ class ApplicationConfigurationViewSet(ModelViewSet):
):
# AI index was just enabled and vector store file does not exist
llmindex_index.delay(
progress_bar_disable=True,
rebuild=True,
scheduled=False,
auto=True,

View File

@@ -1,13 +1,11 @@
import logging
import shutil
from collections.abc import Callable
from collections.abc import Iterable
from datetime import timedelta
from pathlib import Path
from typing import TypeVar
import faiss
import llama_index.core.settings as llama_settings
import tqdm
from celery import states
from django.conf import settings
from django.utils import timezone
@@ -31,14 +29,6 @@ from paperless_ai.embedding import build_llm_index_text
from paperless_ai.embedding import get_embedding_dim
from paperless_ai.embedding import get_embedding_model
_T = TypeVar("_T")
IterWrapper = Callable[[Iterable[_T]], Iterable[_T]]
def _identity(iterable: Iterable[_T]) -> Iterable[_T]:
return iterable
logger = logging.getLogger("paperless_ai.indexing")
@@ -166,11 +156,7 @@ def vector_store_file_exists():
return Path(settings.LLM_INDEX_DIR / "default__vector_store.json").exists()
def update_llm_index(
*,
iter_wrapper: IterWrapper[Document] = _identity,
rebuild=False,
) -> str:
def update_llm_index(*, progress_bar_disable=False, rebuild=False) -> str:
"""
Rebuild or update the LLM index.
"""
@@ -190,7 +176,7 @@ def update_llm_index(
embed_model = get_embedding_model()
llama_settings.Settings.embed_model = embed_model
storage_context = get_or_create_storage_context(rebuild=True)
for document in iter_wrapper(documents):
for document in tqdm.tqdm(documents, disable=progress_bar_disable):
document_nodes = build_document_node(document)
nodes.extend(document_nodes)
@@ -198,7 +184,7 @@ def update_llm_index(
nodes=nodes,
storage_context=storage_context,
embed_model=embed_model,
show_progress=False,
show_progress=not progress_bar_disable,
)
msg = "LLM index rebuilt successfully."
else:
@@ -210,7 +196,7 @@ def update_llm_index(
for node in index.docstore.get_nodes(all_node_ids)
}
for document in iter_wrapper(documents):
for document in tqdm.tqdm(documents, disable=progress_bar_disable):
doc_id = str(document.id)
document_modified = document.modified.isoformat()

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"devDependencies": {
"tailwindcss": "^3.4.19"
"tailwindcss": "^3.4.4"
}
}

6
uv.lock generated
View File

@@ -5906,11 +5906,11 @@ wheels = [
[[package]]
name = "whitenoise"
version = "6.12.0"
version = "6.11.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cb/2a/55b3f3a4ec326cd077c1c3defeee656b9298372a69229134d930151acd01/whitenoise-6.12.0.tar.gz", hash = "sha256:f723ebb76a112e98816ff80fcea0a6c9b8ecde835f8ddda25df7a30a3c2db6ad", size = 26841, upload-time = "2026-02-27T00:05:42.028Z" }
sdist = { url = "https://files.pythonhosted.org/packages/15/95/8c81ec6b6ebcbf8aca2de7603070ccf37dbb873b03f20708e0f7c1664bc6/whitenoise-6.11.0.tar.gz", hash = "sha256:0f5bfce6061ae6611cd9396a8231e088722e4fc67bc13a111be74c738d99375f", size = 26432, upload-time = "2025-09-18T09:16:10.995Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/eb/d5583a11486211f3ebd4b385545ae787f32363d453c19fffd81106c9c138/whitenoise-6.12.0-py3-none-any.whl", hash = "sha256:fc5e8c572e33ebf24795b47b6a7da8da3c00cff2349f5b04c02f28d0cc5a3cc2", size = 20302, upload-time = "2026-02-27T00:05:40.086Z" },
{ url = "https://files.pythonhosted.org/packages/6c/e9/4366332f9295fe0647d7d3251ce18f5615fbcb12d02c79a26f8dba9221b3/whitenoise-6.11.0-py3-none-any.whl", hash = "sha256:b2aeb45950597236f53b5342b3121c5de69c8da0109362aee506ce88e022d258", size = 20197, upload-time = "2025-09-18T09:16:09.754Z" },
]
[[package]]

View File

@@ -36,7 +36,7 @@ extra_css = ["assets/extra.css"]
[project.theme]
logo = "assets/logo_leaf_white.svg"
logo = "assets/logo.svg"
favicon = "assets/favicon.png"
language = "en"