mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-12-10 01:01:17 -06:00
Compare commits
32 Commits
151a4113fd
...
feature-69
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd9c9fe883 | ||
|
|
c416da7319 | ||
|
|
ffbd8010fe | ||
|
|
4d6a4becc1 | ||
|
|
85e2de67b3 | ||
|
|
8417dbef56 | ||
|
|
931e2321b3 | ||
|
|
c5ec073a5b | ||
|
|
e7f7d9e64e | ||
|
|
94d3de37f9 | ||
|
|
53f7f25b11 | ||
|
|
08cbacfefc | ||
|
|
964e606a8e | ||
|
|
b4972d2bca | ||
|
|
08f71a7ed7 | ||
|
|
a59dee4f0b | ||
|
|
a9b5463141 | ||
|
|
0af895ca0a | ||
|
|
cd487ba98b | ||
|
|
f92cb1bcfc | ||
|
|
378cd95016 | ||
|
|
fa2d289615 | ||
|
|
eb28040a0a | ||
|
|
350a7a2946 | ||
|
|
919c54c6ba | ||
|
|
4632ad3a36 | ||
|
|
0c43b50f01 | ||
|
|
67d079fe14 | ||
|
|
ca674e5a02 | ||
|
|
71e08a1e98 | ||
|
|
1e61a6cd6a | ||
|
|
a76731ca89 |
@@ -8,14 +8,17 @@ ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TARGETARCH
|
||||
|
||||
# Can be workflow provided, defaults set for manual building
|
||||
ARG JBIG2ENC_VERSION=0.29
|
||||
ARG JBIG2ENC_VERSION=0.30
|
||||
|
||||
# Set Python environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
# Ignore warning from Whitenoise
|
||||
PYTHONWARNINGS="ignore:::django.http.response:517" \
|
||||
PNGX_CONTAINERIZED=1
|
||||
PNGX_CONTAINERIZED=1 \
|
||||
# https://docs.astral.sh/uv/reference/settings/#link-mode
|
||||
UV_LINK_MODE=copy \
|
||||
UV_CACHE_DIR=/cache/uv/
|
||||
|
||||
#
|
||||
# Begin installation and configuration
|
||||
@@ -81,7 +84,7 @@ RUN set -eux \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes --quiet ${PYTHON_PACKAGES}
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.7.8 /uv /bin/uv
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.9.10 /uv /bin/uv
|
||||
|
||||
|
||||
RUN set -eux \
|
||||
@@ -103,6 +106,7 @@ COPY [ \
|
||||
|
||||
RUN set -eux \
|
||||
&& echo "Configuring ImageMagick" \
|
||||
&& mkdir -p /etc/ImageMagick-6 \
|
||||
&& mv paperless-policy.xml /etc/ImageMagick-6/policy.xml
|
||||
|
||||
|
||||
@@ -118,7 +122,7 @@ ARG BUILD_PACKAGES="\
|
||||
pkg-config"
|
||||
|
||||
# hadolint ignore=DL3042
|
||||
RUN --mount=type=cache,target=/root/.cache/uv,id=pip-cache \
|
||||
RUN --mount=type=cache,target=/cache/uv/,id=uv-cache \
|
||||
set -eux \
|
||||
&& echo "Installing build system packages" \
|
||||
&& apt-get update \
|
||||
|
||||
@@ -32,7 +32,7 @@ RUN set -eux \
|
||||
# Purpose: Installs s6-overlay and rootfs
|
||||
# Comments:
|
||||
# - Don't leave anything extra in here either
|
||||
FROM ghcr.io/astral-sh/uv:0.9.10-python3.12-trixie-slim AS s6-overlay-base
|
||||
FROM ghcr.io/astral-sh/uv:0.9.14-python3.12-trixie-slim AS s6-overlay-base
|
||||
|
||||
WORKDIR /usr/src/s6
|
||||
|
||||
|
||||
@@ -50,9 +50,8 @@ wait_for_mariadb() {
|
||||
|
||||
local -r host="${PAPERLESS_DBHOST:-localhost}"
|
||||
local -r port="${PAPERLESS_DBPORT:-3306}"
|
||||
local -r user="${PAPERLESS_DBUSER:-paperless}"
|
||||
|
||||
while ! mariadb-admin --host="${host}" --port="${port}" --user="${user}" ping --silent >/dev/null 2>&1; do
|
||||
while ! mariadb-admin --host="${host}" --port="${port}" --skip-ssl ping --silent >/dev/null 2>&1; do
|
||||
delay_next_attempt
|
||||
done
|
||||
echo "${LOG_PREFIX} Connected to MariaDB"
|
||||
|
||||
@@ -1593,6 +1593,16 @@ processing. This only has an effect if
|
||||
|
||||
Defaults to `0 1 * * *`, once per day.
|
||||
|
||||
## Share links
|
||||
|
||||
#### [`PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON=<cron expression>`](#PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON) {#PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON}
|
||||
|
||||
: Controls how often Paperless-ngx removes expired share link bundles (and their generated ZIP archives).
|
||||
|
||||
: If set to the string "disable", expired bundles are not cleaned up automatically.
|
||||
|
||||
Defaults to `0 2 * * *`, once per day at 02:00.
|
||||
|
||||
## Binaries
|
||||
|
||||
There are a few external software packages that Paperless expects to
|
||||
|
||||
@@ -286,12 +286,14 @@ or using [email](#workflow-action-email) or [webhook](#workflow-action-webhook)
|
||||
|
||||
### Share Links
|
||||
|
||||
"Share links" are shareable public links to files and can be created and managed under the 'Send' button on the document detail screen.
|
||||
"Share links" are public links to files (or an archive of files) and can be created and managed under the 'Send' button on the document detail screen or from the bulk editor.
|
||||
|
||||
- Share links do not require a user to login and thus link directly to a file.
|
||||
- Share links do not require a user to login and thus link directly to a file or bundled download.
|
||||
- Links are unique and are of the form `{paperless-url}/share/{randomly-generated-slug}`.
|
||||
- Links can optionally have an expiration time set.
|
||||
- After a link expires or is deleted users will be redirected to the regular paperless-ngx login.
|
||||
- From the document detail screen you can create a share link for that single document.
|
||||
- From the bulk editor you can create a **share link bundle** for any selection. Paperless-ngx prepares a ZIP archive in the background and exposes a single share link. You can revisit the "Manage share link bundles" dialog to monitor progress, retry failed bundles, or delete links.
|
||||
|
||||
!!! tip
|
||||
|
||||
|
||||
@@ -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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/alert/alert.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/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<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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/carousel/carousel.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/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,212 +20,212 @@
|
||||
<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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/carousel/carousel.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/carousel/carousel.ts</context>
|
||||
<context context-type="linenumber">157,159</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/carousel/carousel.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/carousel/carousel.ts</context>
|
||||
<context context-type="linenumber">198</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/datepicker/datepicker-navigation.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/datepicker/datepicker-navigation.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/datepicker/datepicker-navigation.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/datepicker/datepicker-navigation.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/datepicker/datepicker-navigation.ts</context>
|
||||
<context context-type="linenumber">112</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</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@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/ngb-config.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
@@ -233,7 +233,7 @@
|
||||
<source><x id="INTERPOLATION" equiv-text="barConfig);
|
||||
pu"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.12_@angular+core@20.3.12_@angula_562237c7eed2d9c47098450f256dc04c/node_modules/src/progressbar/progressbar.ts</context>
|
||||
<context context-type="sourcefile">node_modules/.pnpm/@ng-bootstrap+ng-bootstrap@19.0.1_@angular+common@20.3.14_@angular+core@20.3.12_@angula_f6978d5a33be250eb7b5e8e65faf7a7d/node_modules/src/progressbar/progressbar.ts</context>
|
||||
<context context-type="linenumber">41,42</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^20.2.13",
|
||||
"@angular/common": "~20.3.12",
|
||||
"@angular/common": "~20.3.14",
|
||||
"@angular/compiler": "~20.3.12",
|
||||
"@angular/core": "~20.3.12",
|
||||
"@angular/forms": "~20.3.12",
|
||||
|
||||
158
src-ui/pnpm-lock.yaml
generated
158
src-ui/pnpm-lock.yaml
generated
@@ -10,10 +10,10 @@ importers:
|
||||
dependencies:
|
||||
'@angular/cdk':
|
||||
specifier: ^20.2.13
|
||||
version: 20.2.13(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
version: 20.2.13(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
specifier: ~20.3.14
|
||||
version: 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/compiler':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12
|
||||
@@ -22,28 +22,28 @@ importers:
|
||||
version: 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
version: 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/localize':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)
|
||||
'@angular/platform-browser':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
version: 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser-dynamic':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
version: 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/router':
|
||||
specifier: ~20.3.12
|
||||
version: 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
version: 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@ng-bootstrap/ng-bootstrap':
|
||||
specifier: ^19.0.1
|
||||
version: 19.0.1(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
version: 19.0.1(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
'@ng-select/ng-select':
|
||||
specifier: ^20.7.0
|
||||
version: 20.7.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))
|
||||
version: 20.7.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))
|
||||
'@ngneat/dirty-check-forms':
|
||||
specifier: ^3.0.3
|
||||
version: 3.0.3(30ab5a8fe47ec28a91fa5d2ee68e6d33)
|
||||
version: 3.0.3(8ff1ffec9c0eb3e42a8b58cc79f67aaa)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
@@ -61,19 +61,19 @@ importers:
|
||||
version: 10.4.0
|
||||
ngx-bootstrap-icons:
|
||||
specifier: ^1.9.3
|
||||
version: 1.9.3(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
version: 1.9.3(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
ngx-color:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
version: 10.1.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
ngx-cookie-service:
|
||||
specifier: ^20.1.1
|
||||
version: 20.1.1(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
version: 20.1.1(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
ngx-device-detector:
|
||||
specifier: ^10.1.0
|
||||
version: 10.1.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
version: 10.1.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
ngx-ui-tour-ng-bootstrap:
|
||||
specifier: ^17.0.1
|
||||
version: 17.0.1(ea1ef7845320e16ba2fd99b51fa9f5ad)
|
||||
version: 17.0.1(1990d937b48b356c3a7b81a3382c7bae)
|
||||
rxjs:
|
||||
specifier: ^7.8.2
|
||||
version: 7.8.2
|
||||
@@ -92,10 +92,10 @@ importers:
|
||||
devDependencies:
|
||||
'@angular-builders/custom-webpack':
|
||||
specifier: ^20.0.0
|
||||
version: 20.0.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
version: 20.0.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-builders/jest':
|
||||
specifier: ^20.0.0
|
||||
version: 20.0.0(f235def4c8e1307a0f702d1be69d225c)
|
||||
version: 20.0.0(2a2dc918d42153e5c2c5a3eb18182c36)
|
||||
'@angular-devkit/core':
|
||||
specifier: ^20.3.10
|
||||
version: 20.3.10(chokidar@4.0.3)
|
||||
@@ -119,7 +119,7 @@ importers:
|
||||
version: 20.6.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.8.3)
|
||||
'@angular/build':
|
||||
specifier: ^20.3.10
|
||||
version: 20.3.10(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
version: 20.3.10(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/cli':
|
||||
specifier: ~20.3.10
|
||||
version: 20.3.10(@types/node@24.10.1)(chokidar@4.0.3)
|
||||
@@ -161,7 +161,7 @@ importers:
|
||||
version: 16.0.0
|
||||
jest-preset-angular:
|
||||
specifier: ^15.0.3
|
||||
version: 15.0.3(95a8eae46a7b2bcef52e71596dd4f983)
|
||||
version: 15.0.3(138e950b6256ba9944139a8c5aad3bdf)
|
||||
jest-websocket-mock:
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
@@ -507,11 +507,11 @@ packages:
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
|
||||
hasBin: true
|
||||
|
||||
'@angular/common@20.3.12':
|
||||
resolution: {integrity: sha512-rFcDfe67ffrb435C6t2lc27WGbizeOcgce30tUhH0iezwEvU+kHHWezXXX6Ylx3TFgqGkhcxL0fliuFYrpM1Vw==}
|
||||
'@angular/common@20.3.14':
|
||||
resolution: {integrity: sha512-OOUvjTtnpktJLsNupA+GFT2q5zNocPdpOENA8aSrXvAheNybLjgi+otO3U3sQsvB1VwaoEZ9GT5O3lZlstnA/A==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
peerDependencies:
|
||||
'@angular/core': 20.3.12
|
||||
'@angular/core': 20.3.14
|
||||
rxjs: ^6.5.3 || ^7.4.0
|
||||
|
||||
'@angular/compiler-cli@20.3.12':
|
||||
@@ -7049,13 +7049,13 @@ snapshots:
|
||||
- chokidar
|
||||
- typescript
|
||||
|
||||
'@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
'@angular-builders/custom-webpack@20.0.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@angular-builders/common': 4.0.0(@types/node@24.10.1)(chokidar@4.0.3)(typescript@5.8.3)
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/core': 20.3.10(chokidar@4.0.3)
|
||||
'@angular/build': 20.3.10(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/build': 20.3.10(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/compiler-cli': 20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3)
|
||||
lodash: 4.17.21
|
||||
webpack-merge: 6.0.1
|
||||
@@ -7103,17 +7103,17 @@ snapshots:
|
||||
- webpack-cli
|
||||
- yaml
|
||||
|
||||
'@angular-builders/jest@20.0.0(f235def4c8e1307a0f702d1be69d225c)':
|
||||
'@angular-builders/jest@20.0.0(2a2dc918d42153e5c2c5a3eb18182c36)':
|
||||
dependencies:
|
||||
'@angular-builders/common': 4.0.0(@types/node@24.10.1)(chokidar@4.0.3)(typescript@5.8.3)
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/build-angular': 20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)
|
||||
'@angular-devkit/core': 20.3.10(chokidar@4.0.3)
|
||||
'@angular/compiler-cli': 20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser-dynamic': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/platform-browser-dynamic': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
jest: 30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3))
|
||||
jest-preset-angular: 14.6.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(canvas@3.0.0)(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3)
|
||||
jest-preset-angular: 14.6.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(canvas@3.0.0)(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3)
|
||||
lodash: 4.17.21
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -7145,13 +7145,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- chokidar
|
||||
|
||||
'@angular-devkit/build-angular@20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
'@angular-devkit/build-angular@20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0(canvas@3.0.0))(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jiti@1.21.7)(typescript@5.8.3)(vite@7.1.11(@types/node@24.10.1)(jiti@1.21.7)(less@4.3.0)(sass@1.90.0)(terser@5.39.1)(yaml@2.7.0))(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
'@angular-devkit/build-webpack': 0.2000.4(chokidar@4.0.3)(webpack-dev-server@5.2.1(webpack@5.102.1))(webpack@5.99.8(esbuild@0.25.5))
|
||||
'@angular-devkit/core': 20.0.4(chokidar@4.0.3)
|
||||
'@angular/build': 20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/build': 20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)
|
||||
'@angular/compiler-cli': 20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3)
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/generator': 7.27.1
|
||||
@@ -7207,7 +7207,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/localize': 20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
esbuild: 0.25.5
|
||||
jest: 30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3))
|
||||
jest-environment-jsdom: 30.2.0(canvas@3.0.0)
|
||||
@@ -7339,7 +7339,7 @@ snapshots:
|
||||
eslint: 9.39.1(jiti@1.21.7)
|
||||
typescript: 5.8.3
|
||||
|
||||
'@angular/build@20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
'@angular/build@20.0.4(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.2000.4(chokidar@4.0.3)
|
||||
@@ -7374,7 +7374,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/localize': 20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
less: 4.3.0
|
||||
lmdb: 3.3.0
|
||||
postcss: 8.5.3
|
||||
@@ -7391,7 +7391,7 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@angular/build@20.3.10(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
'@angular/build@20.3.10(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.10.1)(chokidar@4.0.3)(jiti@1.21.7)(less@4.3.0)(postcss@8.5.3)(terser@5.39.1)(tslib@2.8.1)(typescript@5.8.3)(yaml@2.7.0)':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@angular-devkit/architect': 0.2003.10(chokidar@4.0.3)
|
||||
@@ -7426,7 +7426,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/localize': 20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
less: 4.3.0
|
||||
lmdb: 3.4.2
|
||||
postcss: 8.5.3
|
||||
@@ -7443,9 +7443,9 @@ snapshots:
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@angular/cdk@20.2.13(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
|
||||
'@angular/cdk@20.2.13(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
parse5: 8.0.0
|
||||
rxjs: 7.8.2
|
||||
@@ -7476,7 +7476,7 @@ snapshots:
|
||||
- chokidar
|
||||
- supports-color
|
||||
|
||||
'@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
|
||||
'@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
rxjs: 7.8.2
|
||||
@@ -7510,11 +7510,11 @@ snapshots:
|
||||
'@angular/compiler': 20.3.12
|
||||
zone.js: 0.15.1
|
||||
|
||||
'@angular/forms@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
'@angular/forms@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -7529,25 +7529,25 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@angular/platform-browser-dynamic@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))':
|
||||
'@angular/platform-browser-dynamic@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/compiler': 20.3.12
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
tslib: 2.8.1
|
||||
|
||||
'@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))':
|
||||
'@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@angular/router@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
'@angular/router@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
@@ -9351,28 +9351,28 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@ng-bootstrap/ng-bootstrap@19.0.1(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@popperjs/core@2.11.8)(rxjs@7.8.2)':
|
||||
'@ng-bootstrap/ng-bootstrap@19.0.1(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@popperjs/core@2.11.8)(rxjs@7.8.2)':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/forms': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/localize': 20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12)
|
||||
'@popperjs/core': 2.11.8
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@ng-select/ng-select@20.7.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))':
|
||||
'@ng-select/ng-select@20.7.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))':
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/forms': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@ngneat/dirty-check-forms@3.0.3(30ab5a8fe47ec28a91fa5d2ee68e6d33)':
|
||||
'@ngneat/dirty-check-forms@3.0.3(8ff1ffec9c0eb3e42a8b58cc79f67aaa)':
|
||||
dependencies:
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/forms': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/router': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/forms': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/router': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
lodash-es: 4.17.21
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
@@ -12068,11 +12068,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
jest-resolve: 30.2.0
|
||||
|
||||
jest-preset-angular@14.6.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(canvas@3.0.0)(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3):
|
||||
jest-preset-angular@14.6.0(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser-dynamic@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))))(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(canvas@3.0.0)(jest@30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3)))(jsdom@26.1.0(canvas@3.0.0))(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@angular/compiler-cli': 20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser-dynamic': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/platform-browser-dynamic': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
bs-logger: 0.2.6
|
||||
esbuild-wasm: 0.25.10
|
||||
jest: 30.2.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.8.3))
|
||||
@@ -12094,12 +12094,12 @@ snapshots:
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
jest-preset-angular@15.0.3(95a8eae46a7b2bcef52e71596dd4f983):
|
||||
jest-preset-angular@15.0.3(138e950b6256ba9944139a8c5aad3bdf):
|
||||
dependencies:
|
||||
'@angular/compiler-cli': 20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser-dynamic': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@angular/platform-browser': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||
'@angular/platform-browser-dynamic': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/compiler@20.3.12)(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))
|
||||
'@jest/environment-jsdom-abstract': 30.2.0(canvas@3.0.0)(jsdom@26.1.0(canvas@3.0.0))
|
||||
bs-logger: 0.2.6
|
||||
esbuild-wasm: 0.27.0
|
||||
@@ -12767,46 +12767,46 @@ snapshots:
|
||||
pdfjs-dist: 4.8.69
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-bootstrap-icons@1.9.3(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
ngx-bootstrap-icons@1.9.3(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-color@10.1.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
ngx-color@10.1.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@ctrl/tinycolor': 4.2.0
|
||||
material-colors: 1.2.6
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-cookie-service@20.1.1(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
ngx-cookie-service@20.1.1(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-device-detector@10.1.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
ngx-device-detector@10.1.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-ui-tour-core@15.0.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2):
|
||||
ngx-ui-tour-core@15.0.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@angular/router': 20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
'@angular/router': 20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)
|
||||
rxjs: 7.8.2
|
||||
tslib: 2.8.1
|
||||
|
||||
ngx-ui-tour-ng-bootstrap@17.0.1(ea1ef7845320e16ba2fd99b51fa9f5ad):
|
||||
ngx-ui-tour-ng-bootstrap@17.0.1(1990d937b48b356c3a7b81a3382c7bae):
|
||||
dependencies:
|
||||
'@angular/common': 20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/common': 20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)
|
||||
'@angular/core': 20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||
'@ng-bootstrap/ng-bootstrap': 19.0.1(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
ngx-ui-tour-core: 15.0.0(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.12(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2)
|
||||
'@ng-bootstrap/ng-bootstrap': 19.0.1(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/forms@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(@angular/localize@20.3.12(@angular/compiler-cli@20.3.12(@angular/compiler@20.3.12)(typescript@5.8.3))(@angular/compiler@20.3.12))(@popperjs/core@2.11.8)(rxjs@7.8.2)
|
||||
ngx-ui-tour-core: 15.0.0(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.12(@angular/common@20.3.14(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.12(@angular/compiler@20.3.12)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(rxjs@7.8.2)
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- '@angular/router'
|
||||
|
||||
@@ -411,6 +411,9 @@ export class GlobalSearchComponent implements OnInit {
|
||||
const ruleType = this.useAdvancedForFullSearch
|
||||
? FILTER_FULLTEXT_QUERY
|
||||
: FILTER_TITLE_CONTENT
|
||||
this.documentService.searchQuery = this.useAdvancedForFullSearch
|
||||
? this.query
|
||||
: ''
|
||||
this.documentListViewService.quickFilter([
|
||||
{ rule_type: ruleType, value: this.query },
|
||||
])
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ title }}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (!createdBundle) {
|
||||
<form [formGroup]="form" class="d-flex flex-column gap-3">
|
||||
<div>
|
||||
<p class="mb-1">
|
||||
<ng-container i18n>Selected documents:</ng-container>
|
||||
{{ selectionCount }}
|
||||
</p>
|
||||
@if (documentPreview.length > 0) {
|
||||
<ul class="list-unstyled small mb-0">
|
||||
@for (doc of documentPreview; track doc.id) {
|
||||
<li>
|
||||
<strong>{{ doc.title | documentTitle }}</strong>
|
||||
</li>
|
||||
}
|
||||
@if (selectionCount > documentPreview.length) {
|
||||
<li>
|
||||
<ng-container i18n>+ {{ selectionCount - documentPreview.length }} more…</ng-container>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="input-group">
|
||||
<label class="input-group-text" for="expirationDays"><ng-container i18n>Expires</ng-container>:</label>
|
||||
<select class="form-select" id="expirationDays" formControlName="expirationDays">
|
||||
@for (option of expirationOptions; track option.value) {
|
||||
<option [ngValue]="option.value">{{ option.label }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-check form-switch w-100 ms-3">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="shareArchiveSwitch"
|
||||
formControlName="shareArchiveVersion"
|
||||
/>
|
||||
<label class="form-check-label" for="shareArchiveSwitch" i18n>Share archive version (if available)</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
} @else {
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="alert alert-success mb-0" role="status">
|
||||
<h6 class="alert-heading mb-1" i18n>Share link bundle requested</h6>
|
||||
<p class="mb-0 small" i18n>
|
||||
You can copy the share link below or open the manager to monitor progress. The link will start working once the bundle is ready.
|
||||
</p>
|
||||
</div>
|
||||
<dl class="row mb-0 small">
|
||||
<dt class="col-sm-4" i18n>Status</dt>
|
||||
<dd class="col-sm-8">
|
||||
<span class="badge text-bg-secondary text-uppercase">{{ statusLabel(createdBundle.status) }}</span>
|
||||
</dd>
|
||||
<dt class="col-sm-4" i18n>Slug</dt>
|
||||
<dd class="col-sm-8"><code>{{ createdBundle.slug }}</code></dd>
|
||||
<dt class="col-sm-4" i18n>Link</dt>
|
||||
<dd class="col-sm-8">
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
|
||||
<button
|
||||
class="btn btn-outline-primary"
|
||||
type="button"
|
||||
(click)="copy(createdBundle)"
|
||||
>
|
||||
@if (copied) {
|
||||
<i-bs name="clipboard-check"></i-bs>
|
||||
}
|
||||
@if (!copied) {
|
||||
<i-bs name="clipboard"></i-bs>
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy link</span>
|
||||
</button>
|
||||
</div>
|
||||
</dd>
|
||||
<dt class="col-sm-4" i18n>Documents</dt>
|
||||
<dd class="col-sm-8">{{ createdBundle.document_count }}</dd>
|
||||
<dt class="col-sm-4" i18n>Expires</dt>
|
||||
<dd class="col-sm-8">
|
||||
@if (createdBundle.expiration) {
|
||||
{{ createdBundle.expiration | date: 'short' }}
|
||||
}
|
||||
@if (!createdBundle.expiration) {
|
||||
<span i18n>Never</span>
|
||||
}
|
||||
</dd>
|
||||
<dt class="col-sm-4" i18n>File version</dt>
|
||||
<dd class="col-sm-8">{{ fileVersionLabel(createdBundle.file_version) }}</dd>
|
||||
@if (createdBundle.size_bytes !== undefined && createdBundle.size_bytes !== null) {
|
||||
<dt class="col-sm-4" i18n>Size</dt>
|
||||
<dd class="col-sm-8">{{ createdBundle.size_bytes | fileSize }}</dd>
|
||||
}
|
||||
</dl>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="d-flex align-items-center gap-2 w-100">
|
||||
<div class="text-light fst-italic small">
|
||||
<ng-container i18n>A zip file containing the selected documents will be created for this share link bundle. This process happens in the background and may take some time, especially for large bundles.</ng-container>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm ms-auto" (click)="cancel()">{{ cancelBtnCaption }}</button>
|
||||
@if (createdBundle) {
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm text-nowrap" (click)="openManage()" i18n>Manage share link bundles</button>
|
||||
}
|
||||
|
||||
@if (!createdBundle) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2 text-nowrap"
|
||||
(click)="submit()"
|
||||
[disabled]="loading || !buttonsEnabled">
|
||||
@if (loading) {
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||
}
|
||||
{{ btnCaption }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { FileVersion } from 'src/app/data/share-link'
|
||||
import {
|
||||
ShareLinkBundleStatus,
|
||||
ShareLinkBundleSummary,
|
||||
} from 'src/app/data/share-link-bundle'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ShareLinkBundleDialogComponent } from './share-link-bundle-dialog.component'
|
||||
|
||||
class MockToastService {
|
||||
showInfo = jest.fn()
|
||||
showError = jest.fn()
|
||||
}
|
||||
|
||||
describe('ShareLinkBundleDialogComponent', () => {
|
||||
let component: ShareLinkBundleDialogComponent
|
||||
let fixture: ComponentFixture<ShareLinkBundleDialogComponent>
|
||||
let clipboard: Clipboard
|
||||
let toastService: MockToastService
|
||||
let activeModal: NgbActiveModal
|
||||
let originalApiBaseUrl: string
|
||||
|
||||
beforeEach(() => {
|
||||
originalApiBaseUrl = environment.apiBaseUrl
|
||||
toastService = new MockToastService()
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ShareLinkBundleDialogComponent,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{ provide: ToastService, useValue: toastService },
|
||||
],
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(ShareLinkBundleDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
clipboard = TestBed.inject(Clipboard)
|
||||
activeModal = TestBed.inject(NgbActiveModal)
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers()
|
||||
environment.apiBaseUrl = originalApiBaseUrl
|
||||
})
|
||||
|
||||
it('builds payload and emits confirm on submit', () => {
|
||||
const confirmSpy = jest.spyOn(component.confirmClicked, 'emit')
|
||||
component.documents = [
|
||||
{ id: 1, title: 'Doc 1' } as any,
|
||||
{ id: 2, title: 'Doc 2' } as any,
|
||||
]
|
||||
component.form.setValue({
|
||||
shareArchiveVersion: false,
|
||||
expirationDays: 3,
|
||||
})
|
||||
|
||||
component.submit()
|
||||
|
||||
expect(component.payload).toEqual({
|
||||
document_ids: [1, 2],
|
||||
file_version: FileVersion.Original,
|
||||
expiration_days: 3,
|
||||
})
|
||||
expect(component.buttonsEnabled).toBe(false)
|
||||
expect(confirmSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('ignores submit when bundle already created', () => {
|
||||
component.createdBundle = { id: 1 } as ShareLinkBundleSummary
|
||||
const confirmSpy = jest.spyOn(component, 'confirm')
|
||||
component.submit()
|
||||
expect(confirmSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('limits preview to ten documents', () => {
|
||||
const docs = Array.from({ length: 12 }).map((_, index) => ({
|
||||
id: index + 1,
|
||||
}))
|
||||
component.documents = docs as any
|
||||
|
||||
expect(component.selectionCount).toBe(12)
|
||||
expect(component.documentPreview).toHaveLength(10)
|
||||
expect(component.documentPreview[0].id).toBe(1)
|
||||
})
|
||||
|
||||
it('copies share link and resets state after timeout', fakeAsync(() => {
|
||||
const copySpy = jest.spyOn(clipboard, 'copy').mockReturnValue(true)
|
||||
const bundle = {
|
||||
slug: 'bundle-slug',
|
||||
status: ShareLinkBundleStatus.Ready,
|
||||
} as ShareLinkBundleSummary
|
||||
|
||||
component.copy(bundle)
|
||||
|
||||
expect(copySpy).toHaveBeenCalledWith(component.getShareUrl(bundle))
|
||||
expect(component.copied).toBe(true)
|
||||
expect(toastService.showInfo).toHaveBeenCalled()
|
||||
|
||||
tick(3000)
|
||||
expect(component.copied).toBe(false)
|
||||
}))
|
||||
|
||||
it('generates share URLs based on API base URL', () => {
|
||||
environment.apiBaseUrl = 'https://example.com/api/'
|
||||
expect(
|
||||
component.getShareUrl({ slug: 'abc' } as ShareLinkBundleSummary)
|
||||
).toBe('https://example.com/share/abc')
|
||||
})
|
||||
|
||||
it('opens manage dialog when callback provided', () => {
|
||||
const manageSpy = jest.fn()
|
||||
component.onOpenManage = manageSpy
|
||||
component.openManage()
|
||||
expect(manageSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('falls back to cancel when manage callback missing', () => {
|
||||
const cancelSpy = jest.spyOn(component, 'cancel')
|
||||
component.onOpenManage = undefined
|
||||
component.openManage()
|
||||
expect(cancelSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('maps status and file version labels', () => {
|
||||
expect(component.statusLabel(ShareLinkBundleStatus.Processing)).toContain(
|
||||
'Processing'
|
||||
)
|
||||
expect(component.fileVersionLabel(FileVersion.Archive)).toContain('Archive')
|
||||
})
|
||||
|
||||
it('closes dialog when cancel invoked', () => {
|
||||
const closeSpy = jest.spyOn(activeModal, 'close')
|
||||
component.cancel()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Component, Input, inject } from '@angular/core'
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Document } from 'src/app/data/document'
|
||||
import {
|
||||
FileVersion,
|
||||
SHARE_LINK_EXPIRATION_OPTIONS,
|
||||
} from 'src/app/data/share-link'
|
||||
import {
|
||||
SHARE_LINK_BUNDLE_FILE_VERSION_LABELS,
|
||||
SHARE_LINK_BUNDLE_STATUS_LABELS,
|
||||
ShareLinkBundleCreatePayload,
|
||||
ShareLinkBundleStatus,
|
||||
ShareLinkBundleSummary,
|
||||
} from 'src/app/data/share-link-bundle'
|
||||
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
|
||||
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-share-link-bundle-dialog',
|
||||
templateUrl: './share-link-bundle-dialog.component.html',
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
NgxBootstrapIconsModule,
|
||||
FileSizePipe,
|
||||
DocumentTitlePipe,
|
||||
],
|
||||
providers: [],
|
||||
})
|
||||
export class ShareLinkBundleDialogComponent extends ConfirmDialogComponent {
|
||||
private formBuilder = inject(FormBuilder)
|
||||
private clipboard = inject(Clipboard)
|
||||
private toastService = inject(ToastService)
|
||||
|
||||
private _documents: Document[] = []
|
||||
|
||||
selectionCount = 0
|
||||
documentPreview: Document[] = []
|
||||
form: FormGroup = this.formBuilder.group({
|
||||
shareArchiveVersion: [true],
|
||||
expirationDays: [7],
|
||||
})
|
||||
payload: ShareLinkBundleCreatePayload | null = null
|
||||
|
||||
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
||||
|
||||
createdBundle: ShareLinkBundleSummary | null = null
|
||||
copied = false
|
||||
onOpenManage?: () => void
|
||||
readonly statuses = ShareLinkBundleStatus
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.loading = false
|
||||
this.title = $localize`Create share link bundle`
|
||||
this.btnCaption = $localize`Create link`
|
||||
}
|
||||
|
||||
@Input()
|
||||
set documents(docs: Document[]) {
|
||||
this._documents = docs.concat()
|
||||
this.selectionCount = this._documents.length
|
||||
this.documentPreview = this._documents.slice(0, 10)
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.createdBundle) return
|
||||
this.payload = {
|
||||
document_ids: this._documents.map((doc) => doc.id),
|
||||
file_version: this.form.value.shareArchiveVersion
|
||||
? FileVersion.Archive
|
||||
: FileVersion.Original,
|
||||
expiration_days: this.form.value.expirationDays,
|
||||
}
|
||||
this.buttonsEnabled = false
|
||||
super.confirm()
|
||||
}
|
||||
|
||||
getShareUrl(bundle: ShareLinkBundleSummary): string {
|
||||
const apiURL = new URL(environment.apiBaseUrl)
|
||||
return `${apiURL.origin}${apiURL.pathname.replace(/\/api\/$/, '/share/')}${
|
||||
bundle.slug
|
||||
}`
|
||||
}
|
||||
|
||||
copy(bundle: ShareLinkBundleSummary): void {
|
||||
const success = this.clipboard.copy(this.getShareUrl(bundle))
|
||||
if (success) {
|
||||
this.copied = true
|
||||
this.toastService.showInfo($localize`Share link copied to clipboard.`)
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
|
||||
openManage(): void {
|
||||
if (this.onOpenManage) {
|
||||
this.onOpenManage()
|
||||
} else {
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
statusLabel(status: ShareLinkBundleSummary['status']): string {
|
||||
return SHARE_LINK_BUNDLE_STATUS_LABELS[status] ?? status
|
||||
}
|
||||
|
||||
fileVersionLabel(version: FileVersion): string {
|
||||
return SHARE_LINK_BUNDLE_FILE_VERSION_LABELS[version] ?? version
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{{ title }}</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="close()"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
@if (loading) {
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
<span i18n>Loading share link bundles…</span>
|
||||
</div>
|
||||
}
|
||||
@if (!loading && error) {
|
||||
<div class="alert alert-danger mb-0" role="alert">
|
||||
{{ error }}
|
||||
</div>
|
||||
}
|
||||
@if (!loading && !error) {
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<p class="mb-0 text-muted small">
|
||||
<ng-container i18n>Status updates every few seconds while bundles are being prepared.</ng-container>
|
||||
</p>
|
||||
</div>
|
||||
@if (bundles.length === 0) {
|
||||
<p class="mb-0 text-muted fst-italic" i18n>No share link bundles currently exist.</p>
|
||||
}
|
||||
@if (bundles.length > 0) {
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" i18n>Created</th>
|
||||
<th scope="col" i18n>Status</th>
|
||||
<th scope="col" i18n>Size</th>
|
||||
<th scope="col" i18n>Expires</th>
|
||||
<th scope="col" i18n>Documents</th>
|
||||
<th scope="col" i18n>File version</th>
|
||||
<th scope="col" class="text-end" i18n>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (bundle of bundles; track bundle.id) {
|
||||
<tr>
|
||||
<td>
|
||||
<div>{{ bundle.created | date: 'short' }}</div>
|
||||
@if (bundle.built_at) {
|
||||
<div class="small text-muted">
|
||||
<ng-container i18n>Built:</ng-container> {{ bundle.built_at | date: 'short' }}
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (bundle.status === statuses.Processing || bundle.status === statuses.Pending) {
|
||||
<span class="spinner-border spinner-border-sm" role="status"></span>
|
||||
}
|
||||
<span class="badge text-bg-secondary text-uppercase">{{ statusLabel(bundle.status) }}</span>
|
||||
</div>
|
||||
@if (bundle.last_error && bundle.status === statuses.Failed) {
|
||||
<div class="small text-danger mt-1">{{ bundle.last_error }}</div>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (bundle.size_bytes !== undefined && bundle.size_bytes !== null) {
|
||||
{{ bundle.size_bytes | fileSize }}
|
||||
}
|
||||
@if (bundle.size_bytes === undefined || bundle.size_bytes === null) {
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (bundle.expiration) {
|
||||
{{ bundle.expiration | date: 'short' }}
|
||||
}
|
||||
@if (!bundle.expiration) {
|
||||
<span i18n>Never</span>
|
||||
}
|
||||
</td>
|
||||
<td>{{ bundle.document_count }}</td>
|
||||
<td>{{ fileVersionLabel(bundle.file_version) }}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
[disabled]="bundle.status !== statuses.Ready"
|
||||
(click)="copy(bundle)"
|
||||
>
|
||||
@if (copiedSlug === bundle.slug) {
|
||||
<i-bs name="clipboard-check"></i-bs>
|
||||
}
|
||||
@if (copiedSlug !== bundle.slug) {
|
||||
<i-bs name="clipboard"></i-bs>
|
||||
}
|
||||
<span class="visually-hidden" i18n>Copy share link</span>
|
||||
</button>
|
||||
@if (bundle.status === statuses.Failed) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-warning"
|
||||
[disabled]="loading"
|
||||
(click)="retry(bundle)"
|
||||
>
|
||||
<i-bs name="arrow-clockwise"></i-bs>
|
||||
<span class="visually-hidden" i18n>Retry</span>
|
||||
</button>
|
||||
}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-danger"
|
||||
[disabled]="loading"
|
||||
(click)="delete(bundle)"
|
||||
>
|
||||
<i-bs name="trash"></i-bs>
|
||||
<span class="visually-hidden" i18n>Delete share link bundle</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" (click)="close()" i18n>Close</button>
|
||||
</div>
|
||||
@@ -0,0 +1,250 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { of, throwError } from 'rxjs'
|
||||
import { FileVersion } from 'src/app/data/share-link'
|
||||
import {
|
||||
ShareLinkBundleStatus,
|
||||
ShareLinkBundleSummary,
|
||||
} from 'src/app/data/share-link-bundle'
|
||||
import { ShareLinkBundleService } from 'src/app/services/rest/share-link-bundle.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { ShareLinkBundleManageDialogComponent } from './share-link-bundle-manage-dialog.component'
|
||||
|
||||
class MockShareLinkBundleService {
|
||||
listAllBundles = jest.fn()
|
||||
delete = jest.fn()
|
||||
rebuildBundle = jest.fn()
|
||||
}
|
||||
|
||||
class MockToastService {
|
||||
showInfo = jest.fn()
|
||||
showError = jest.fn()
|
||||
}
|
||||
|
||||
describe('ShareLinkBundleManageDialogComponent', () => {
|
||||
let component: ShareLinkBundleManageDialogComponent
|
||||
let fixture: ComponentFixture<ShareLinkBundleManageDialogComponent>
|
||||
let service: MockShareLinkBundleService
|
||||
let toastService: MockToastService
|
||||
let clipboard: Clipboard
|
||||
let activeModal: NgbActiveModal
|
||||
let originalApiBaseUrl: string
|
||||
|
||||
beforeEach(() => {
|
||||
service = new MockShareLinkBundleService()
|
||||
toastService = new MockToastService()
|
||||
originalApiBaseUrl = environment.apiBaseUrl
|
||||
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
service.delete.mockReturnValue(of(true))
|
||||
service.rebuildBundle.mockReturnValue(of(sampleBundle()))
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
ShareLinkBundleManageDialogComponent,
|
||||
NgxBootstrapIconsModule.pick(allIcons),
|
||||
],
|
||||
providers: [
|
||||
NgbActiveModal,
|
||||
{ provide: ShareLinkBundleService, useValue: service },
|
||||
{ provide: ToastService, useValue: toastService },
|
||||
],
|
||||
})
|
||||
|
||||
fixture = TestBed.createComponent(ShareLinkBundleManageDialogComponent)
|
||||
component = fixture.componentInstance
|
||||
clipboard = TestBed.inject(Clipboard)
|
||||
activeModal = TestBed.inject(NgbActiveModal)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
component.ngOnDestroy()
|
||||
fixture.destroy()
|
||||
environment.apiBaseUrl = originalApiBaseUrl
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
const sampleBundle = (overrides: Partial<ShareLinkBundleSummary> = {}) =>
|
||||
({
|
||||
id: 1,
|
||||
slug: 'bundle-slug',
|
||||
created: new Date().toISOString(),
|
||||
document_count: 1,
|
||||
documents: [1],
|
||||
status: ShareLinkBundleStatus.Pending,
|
||||
file_version: FileVersion.Archive,
|
||||
...overrides,
|
||||
}) as ShareLinkBundleSummary
|
||||
|
||||
it('loads bundles on init and polls periodically', fakeAsync(() => {
|
||||
const bundles = [sampleBundle({ status: ShareLinkBundleStatus.Ready })]
|
||||
service.listAllBundles.mockReset()
|
||||
service.listAllBundles
|
||||
.mockReturnValueOnce(of(bundles))
|
||||
.mockReturnValue(of(bundles))
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
expect(service.listAllBundles).toHaveBeenCalledTimes(1)
|
||||
expect(component.bundles).toEqual(bundles)
|
||||
expect(component.loading).toBe(false)
|
||||
expect(component.error).toBeNull()
|
||||
|
||||
tick(5000)
|
||||
expect(service.listAllBundles).toHaveBeenCalledTimes(2)
|
||||
}))
|
||||
|
||||
it('handles errors when loading bundles', fakeAsync(() => {
|
||||
service.listAllBundles.mockReset()
|
||||
service.listAllBundles
|
||||
.mockReturnValueOnce(throwError(() => new Error('load fail')))
|
||||
.mockReturnValue(of([]))
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
expect(component.error).toContain('Failed to load share link bundles.')
|
||||
expect(toastService.showError).toHaveBeenCalled()
|
||||
expect(component.loading).toBe(false)
|
||||
|
||||
tick(5000)
|
||||
expect(service.listAllBundles).toHaveBeenCalledTimes(2)
|
||||
}))
|
||||
|
||||
it('copies bundle links when ready', fakeAsync(() => {
|
||||
jest.spyOn(clipboard, 'copy').mockReturnValue(true)
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
const readyBundle = sampleBundle({
|
||||
slug: 'ready-slug',
|
||||
status: ShareLinkBundleStatus.Ready,
|
||||
})
|
||||
component.copy(readyBundle)
|
||||
|
||||
expect(clipboard.copy).toHaveBeenCalledWith(
|
||||
component.getShareUrl(readyBundle)
|
||||
)
|
||||
expect(component.copiedSlug).toBe('ready-slug')
|
||||
expect(toastService.showInfo).toHaveBeenCalled()
|
||||
|
||||
tick(3000)
|
||||
expect(component.copiedSlug).toBeNull()
|
||||
}))
|
||||
|
||||
it('ignores copy requests for non-ready bundles', fakeAsync(() => {
|
||||
const copySpy = jest.spyOn(clipboard, 'copy')
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
component.copy(sampleBundle({ status: ShareLinkBundleStatus.Pending }))
|
||||
expect(copySpy).not.toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('deletes bundles and refreshes list', fakeAsync(() => {
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
service.delete.mockReturnValue(of(true))
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.delete(sampleBundle())
|
||||
tick()
|
||||
|
||||
expect(service.delete).toHaveBeenCalled()
|
||||
expect(toastService.showInfo).toHaveBeenCalledWith(
|
||||
expect.stringContaining('deleted.')
|
||||
)
|
||||
expect(service.listAllBundles).toHaveBeenCalledTimes(2)
|
||||
expect(component.loading).toBe(false)
|
||||
}))
|
||||
|
||||
it('handles delete errors gracefully', fakeAsync(() => {
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
service.delete.mockReturnValue(throwError(() => new Error('delete fail')))
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.delete(sampleBundle())
|
||||
tick()
|
||||
|
||||
expect(toastService.showError).toHaveBeenCalled()
|
||||
expect(component.loading).toBe(false)
|
||||
}))
|
||||
|
||||
it('retries bundle build and replaces existing entry', fakeAsync(() => {
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
const updated = sampleBundle({ status: ShareLinkBundleStatus.Ready })
|
||||
service.rebuildBundle.mockReturnValue(of(updated))
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.bundles = [sampleBundle()]
|
||||
component.retry(component.bundles[0])
|
||||
tick()
|
||||
|
||||
expect(service.rebuildBundle).toHaveBeenCalledWith(updated.id)
|
||||
expect(component.bundles[0].status).toBe(ShareLinkBundleStatus.Ready)
|
||||
expect(toastService.showInfo).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('adds new bundle when retry returns unknown entry', fakeAsync(() => {
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
service.rebuildBundle.mockReturnValue(
|
||||
of(sampleBundle({ id: 99, slug: 'new-slug' }))
|
||||
)
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.bundles = [sampleBundle()]
|
||||
component.retry({ id: 99 } as ShareLinkBundleSummary)
|
||||
tick()
|
||||
|
||||
expect(component.bundles.find((bundle) => bundle.id === 99)).toBeTruthy()
|
||||
}))
|
||||
|
||||
it('handles retry errors', fakeAsync(() => {
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
service.rebuildBundle.mockReturnValue(throwError(() => new Error('fail')))
|
||||
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
component.retry(sampleBundle())
|
||||
tick()
|
||||
|
||||
expect(toastService.showError).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('maps helpers and closes dialog', fakeAsync(() => {
|
||||
service.listAllBundles.mockReturnValue(of([]))
|
||||
fixture.detectChanges()
|
||||
tick()
|
||||
|
||||
expect(component.statusLabel(ShareLinkBundleStatus.Processing)).toContain(
|
||||
'Processing'
|
||||
)
|
||||
expect(component.fileVersionLabel(FileVersion.Original)).toContain(
|
||||
'Original'
|
||||
)
|
||||
|
||||
environment.apiBaseUrl = 'https://example.com/api/'
|
||||
const url = component.getShareUrl(sampleBundle({ slug: 'sluggy' }))
|
||||
expect(url).toBe('https://example.com/share/sluggy')
|
||||
|
||||
const closeSpy = jest.spyOn(activeModal, 'close')
|
||||
component.close()
|
||||
expect(closeSpy).toHaveBeenCalled()
|
||||
}))
|
||||
})
|
||||
@@ -0,0 +1,169 @@
|
||||
import { Clipboard } from '@angular/cdk/clipboard'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Subject, catchError, of, switchMap, takeUntil, timer } from 'rxjs'
|
||||
import { FileVersion } from 'src/app/data/share-link'
|
||||
import {
|
||||
SHARE_LINK_BUNDLE_FILE_VERSION_LABELS,
|
||||
SHARE_LINK_BUNDLE_STATUS_LABELS,
|
||||
ShareLinkBundleStatus,
|
||||
ShareLinkBundleSummary,
|
||||
} from 'src/app/data/share-link-bundle'
|
||||
import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
|
||||
import { ShareLinkBundleService } from 'src/app/services/rest/share-link-bundle.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { LoadingComponentWithPermissions } from '../../loading-component/loading.component'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-share-link-bundle-manage-dialog',
|
||||
templateUrl: './share-link-bundle-manage-dialog.component.html',
|
||||
imports: [CommonModule, NgxBootstrapIconsModule, FileSizePipe],
|
||||
})
|
||||
export class ShareLinkBundleManageDialogComponent
|
||||
extends LoadingComponentWithPermissions
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
private activeModal = inject(NgbActiveModal)
|
||||
private shareLinkBundleService = inject(ShareLinkBundleService)
|
||||
private toastService = inject(ToastService)
|
||||
private clipboard = inject(Clipboard)
|
||||
|
||||
title = $localize`Share link bundles`
|
||||
|
||||
bundles: ShareLinkBundleSummary[] = []
|
||||
error: string | null = null
|
||||
copiedSlug: string | null = null
|
||||
|
||||
readonly statuses = ShareLinkBundleStatus
|
||||
readonly fileVersions = FileVersion
|
||||
|
||||
private readonly refresh$ = new Subject<boolean>()
|
||||
|
||||
ngOnInit(): void {
|
||||
this.refresh$
|
||||
.pipe(
|
||||
switchMap((silent) => {
|
||||
if (!silent) {
|
||||
this.loading = true
|
||||
}
|
||||
this.error = null
|
||||
return this.shareLinkBundleService.listAllBundles().pipe(
|
||||
catchError((error) => {
|
||||
if (!silent) {
|
||||
this.loading = false
|
||||
}
|
||||
this.error = $localize`Failed to load share link bundles.`
|
||||
this.toastService.showError(
|
||||
$localize`Error retrieving share link bundles.`,
|
||||
error
|
||||
)
|
||||
return of(null)
|
||||
})
|
||||
)
|
||||
}),
|
||||
takeUntil(this.unsubscribeNotifier)
|
||||
)
|
||||
.subscribe((results) => {
|
||||
if (results) {
|
||||
this.bundles = results
|
||||
this.copiedSlug = null
|
||||
}
|
||||
this.loading = false
|
||||
})
|
||||
|
||||
this.triggerRefresh(false)
|
||||
timer(5000, 5000)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => this.triggerRefresh(true))
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
super.ngOnDestroy()
|
||||
}
|
||||
|
||||
getShareUrl(bundle: ShareLinkBundleSummary): string {
|
||||
const apiURL = new URL(environment.apiBaseUrl)
|
||||
return `${apiURL.origin}${apiURL.pathname.replace(/\/api\/$/, '/share/')}${
|
||||
bundle.slug
|
||||
}`
|
||||
}
|
||||
|
||||
copy(bundle: ShareLinkBundleSummary): void {
|
||||
if (bundle.status !== ShareLinkBundleStatus.Ready) {
|
||||
return
|
||||
}
|
||||
const success = this.clipboard.copy(this.getShareUrl(bundle))
|
||||
if (success) {
|
||||
this.copiedSlug = bundle.slug
|
||||
setTimeout(() => {
|
||||
this.copiedSlug = null
|
||||
}, 3000)
|
||||
this.toastService.showInfo($localize`Share link copied to clipboard.`)
|
||||
}
|
||||
}
|
||||
|
||||
delete(bundle: ShareLinkBundleSummary): void {
|
||||
this.error = null
|
||||
this.loading = true
|
||||
this.shareLinkBundleService.delete(bundle).subscribe({
|
||||
next: () => {
|
||||
this.toastService.showInfo($localize`Share link bundle deleted.`)
|
||||
this.triggerRefresh(false)
|
||||
},
|
||||
error: (e) => {
|
||||
this.loading = false
|
||||
this.toastService.showError(
|
||||
$localize`Error deleting share link bundle.`,
|
||||
e
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
retry(bundle: ShareLinkBundleSummary): void {
|
||||
this.error = null
|
||||
this.shareLinkBundleService.rebuildBundle(bundle.id).subscribe({
|
||||
next: (updated) => {
|
||||
this.toastService.showInfo(
|
||||
$localize`Share link bundle rebuild requested.`
|
||||
)
|
||||
this.replaceBundle(updated)
|
||||
},
|
||||
error: (e) => {
|
||||
this.toastService.showError($localize`Error requesting rebuild.`, e)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
statusLabel(status: ShareLinkBundleStatus): string {
|
||||
return SHARE_LINK_BUNDLE_STATUS_LABELS[status] ?? status
|
||||
}
|
||||
|
||||
fileVersionLabel(version: FileVersion): string {
|
||||
return SHARE_LINK_BUNDLE_FILE_VERSION_LABELS[version] ?? version
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.activeModal.close()
|
||||
}
|
||||
|
||||
private replaceBundle(updated: ShareLinkBundleSummary): void {
|
||||
const index = this.bundles.findIndex((bundle) => bundle.id === updated.id)
|
||||
if (index >= 0) {
|
||||
this.bundles = [
|
||||
...this.bundles.slice(0, index),
|
||||
updated,
|
||||
...this.bundles.slice(index + 1),
|
||||
]
|
||||
} else {
|
||||
this.bundles = [updated, ...this.bundles]
|
||||
}
|
||||
}
|
||||
|
||||
private triggerRefresh(silent: boolean): void {
|
||||
this.refresh$.next(silent)
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="input-group w-100 mt-2">
|
||||
<label class="input-group-text" for="addLink"><ng-container i18n>Expires</ng-container>:</label>
|
||||
<select class="form-select fs-6" [(ngModel)]="expirationDays">
|
||||
@for (option of EXPIRATION_OPTIONS; track option) {
|
||||
@for (option of expirationOptions; track option) {
|
||||
<option [ngValue]="option.value">{{ option.label }}</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@@ -4,7 +4,11 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { first } from 'rxjs'
|
||||
import { FileVersion, ShareLink } from 'src/app/data/share-link'
|
||||
import {
|
||||
FileVersion,
|
||||
SHARE_LINK_EXPIRATION_OPTIONS,
|
||||
ShareLink,
|
||||
} from 'src/app/data/share-link'
|
||||
import { ShareLinkService } from 'src/app/services/rest/share-link.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { environment } from 'src/environments/environment'
|
||||
@@ -21,12 +25,7 @@ export class ShareLinksDialogComponent implements OnInit {
|
||||
private toastService = inject(ToastService)
|
||||
private clipboard = inject(Clipboard)
|
||||
|
||||
EXPIRATION_OPTIONS = [
|
||||
{ label: $localize`1 day`, value: 1 },
|
||||
{ label: $localize`7 days`, value: 7 },
|
||||
{ label: $localize`30 days`, value: 30 },
|
||||
{ label: $localize`Never`, value: null },
|
||||
]
|
||||
readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
|
||||
|
||||
@Input()
|
||||
title = $localize`Share Links`
|
||||
|
||||
@@ -96,14 +96,36 @@
|
||||
<button ngbDropdownItem (click)="mergeSelected()" [disabled]="!userCanAdd || list.selected.size < 2">
|
||||
<i-bs name="journals"></i-bs> <ng-container i18n>Merge</ng-container>
|
||||
</button>
|
||||
@if (emailEnabled) {
|
||||
<button ngbDropdownItem (click)="emailSelected()">
|
||||
<i-bs name="envelope"></i-bs> <ng-container i18n>Email</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-toolbar" ngbDropdown>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
id="dropdownSend"
|
||||
ngbDropdownToggle
|
||||
[disabled]="disabled || list.selected.size === 0"
|
||||
>
|
||||
<i-bs name="send"></i-bs>
|
||||
<div class="d-none d-sm-inline">
|
||||
<ng-container i18n>Send</ng-container>
|
||||
</div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="dropdownSend" class="shadow">
|
||||
<button ngbDropdownItem (click)="createShareLinkBundle()">
|
||||
<i-bs name="link"></i-bs> <ng-container i18n>Create a share link bundle</ng-container>
|
||||
</button>
|
||||
<button ngbDropdownItem (click)="manageShareLinkBundles()">
|
||||
<i-bs name="list-ul"></i-bs> <ng-container i18n>Manage share link bundles</ng-container>
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
@if (emailEnabled) {
|
||||
<button ngbDropdownItem (click)="emailSelected()">
|
||||
<i-bs name="envelope"></i-bs> <ng-container i18n>Email</ng-container>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="btn btn-sm btn-outline-primary" [disabled]="awaitingDownload" (click)="downloadSelected()">
|
||||
@if (!awaitingDownload) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
HttpTestingController,
|
||||
provideHttpClientTesting,
|
||||
} from '@angular/common/http/testing'
|
||||
import { EventEmitter } from '@angular/core'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
SelectionData,
|
||||
} from 'src/app/services/rest/document.service'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { ShareLinkBundleService } from 'src/app/services/rest/share-link-bundle.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
@@ -38,6 +40,8 @@ import { EditDialogMode } from '../../common/edit-dialog/edit-dialog.component'
|
||||
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
|
||||
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||
import { FilterableDropdownComponent } from '../../common/filterable-dropdown/filterable-dropdown.component'
|
||||
import { ShareLinkBundleDialogComponent } from '../../common/share-link-bundle-dialog/share-link-bundle-dialog.component'
|
||||
import { ShareLinkBundleManageDialogComponent } from '../../common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component'
|
||||
import { BulkEditorComponent } from './bulk-editor.component'
|
||||
|
||||
const selectionData: SelectionData = {
|
||||
@@ -72,6 +76,7 @@ describe('BulkEditorComponent', () => {
|
||||
let storagePathService: StoragePathService
|
||||
let customFieldsService: CustomFieldsService
|
||||
let httpTestingController: HttpTestingController
|
||||
let shareLinkBundleService: ShareLinkBundleService
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -152,6 +157,15 @@ describe('BulkEditorComponent', () => {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ShareLinkBundleService,
|
||||
useValue: {
|
||||
createBundle: jest.fn(),
|
||||
listAllBundles: jest.fn(),
|
||||
rebuildBundle: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
provideHttpClient(withInterceptorsFromDi()),
|
||||
provideHttpClientTesting(),
|
||||
],
|
||||
@@ -168,6 +182,7 @@ describe('BulkEditorComponent', () => {
|
||||
storagePathService = TestBed.inject(StoragePathService)
|
||||
customFieldsService = TestBed.inject(CustomFieldsService)
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
shareLinkBundleService = TestBed.inject(ShareLinkBundleService)
|
||||
|
||||
fixture = TestBed.createComponent(BulkEditorComponent)
|
||||
component = fixture.componentInstance
|
||||
@@ -1454,4 +1469,130 @@ describe('BulkEditorComponent', () => {
|
||||
`${environment.apiBaseUrl}documents/?page=1&page_size=100000&fields=id`
|
||||
) // listAllFilteredIds
|
||||
})
|
||||
|
||||
it('should create share link bundle and enable manage callback', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(documentListViewService, 'documents', 'get')
|
||||
.mockReturnValue([{ id: 5 }, { id: 7 }] as any)
|
||||
jest
|
||||
.spyOn(documentListViewService, 'selected', 'get')
|
||||
.mockReturnValue(new Set([5, 7]))
|
||||
|
||||
const confirmClicked = new EventEmitter<void>()
|
||||
const modalRef: Partial<NgbModalRef> = {
|
||||
close: jest.fn(),
|
||||
componentInstance: {
|
||||
documents: [],
|
||||
confirmClicked,
|
||||
payload: {
|
||||
document_ids: [5, 7],
|
||||
file_version: 'archive',
|
||||
expiration_days: 7,
|
||||
},
|
||||
loading: false,
|
||||
buttonsEnabled: true,
|
||||
copied: false,
|
||||
},
|
||||
}
|
||||
|
||||
const openSpy = jest.spyOn(modalService, 'open')
|
||||
openSpy.mockReturnValueOnce(modalRef as NgbModalRef)
|
||||
openSpy.mockReturnValueOnce({} as NgbModalRef)
|
||||
;(shareLinkBundleService.createBundle as jest.Mock).mockReturnValueOnce(
|
||||
of({ id: 42 })
|
||||
)
|
||||
const toastInfoSpy = jest.spyOn(toastService, 'showInfo')
|
||||
|
||||
component.createShareLinkBundle()
|
||||
|
||||
expect(openSpy).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
ShareLinkBundleDialogComponent,
|
||||
expect.objectContaining({ backdrop: 'static', size: 'lg' })
|
||||
)
|
||||
|
||||
const dialogInstance = modalRef.componentInstance as any
|
||||
expect(dialogInstance.documents).toEqual([{ id: 5 }, { id: 7 }])
|
||||
|
||||
confirmClicked.emit()
|
||||
|
||||
expect(shareLinkBundleService.createBundle).toHaveBeenCalledWith({
|
||||
document_ids: [5, 7],
|
||||
file_version: 'archive',
|
||||
expiration_days: 7,
|
||||
})
|
||||
expect(dialogInstance.loading).toBe(false)
|
||||
expect(dialogInstance.buttonsEnabled).toBe(false)
|
||||
expect(dialogInstance.createdBundle).toEqual({ id: 42 })
|
||||
expect(typeof dialogInstance.onOpenManage).toBe('function')
|
||||
expect(toastInfoSpy).toHaveBeenCalledWith(
|
||||
$localize`Share link bundle creation requested.`
|
||||
)
|
||||
|
||||
dialogInstance.onOpenManage()
|
||||
expect(modalRef.close).toHaveBeenCalled()
|
||||
expect(openSpy).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
ShareLinkBundleManageDialogComponent,
|
||||
expect.objectContaining({ backdrop: 'static', size: 'lg' })
|
||||
)
|
||||
openSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('should handle share link bundle creation errors', () => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
jest
|
||||
.spyOn(documentListViewService, 'documents', 'get')
|
||||
.mockReturnValue([{ id: 9 }] as any)
|
||||
jest
|
||||
.spyOn(documentListViewService, 'selected', 'get')
|
||||
.mockReturnValue(new Set([9]))
|
||||
|
||||
const confirmClicked = new EventEmitter<void>()
|
||||
const modalRef: Partial<NgbModalRef> = {
|
||||
componentInstance: {
|
||||
documents: [],
|
||||
confirmClicked,
|
||||
payload: {
|
||||
document_ids: [9],
|
||||
file_version: 'original',
|
||||
expiration_days: null,
|
||||
},
|
||||
loading: false,
|
||||
buttonsEnabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
const openSpy = jest
|
||||
.spyOn(modalService, 'open')
|
||||
.mockReturnValue(modalRef as NgbModalRef)
|
||||
;(shareLinkBundleService.createBundle as jest.Mock).mockReturnValueOnce(
|
||||
throwError(() => new Error('bundle failure'))
|
||||
)
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
|
||||
component.createShareLinkBundle()
|
||||
|
||||
const dialogInstance = modalRef.componentInstance as any
|
||||
confirmClicked.emit()
|
||||
|
||||
expect(toastErrorSpy).toHaveBeenCalledWith(
|
||||
$localize`Share link bundle creation is not available yet.`,
|
||||
expect.any(Error)
|
||||
)
|
||||
expect(dialogInstance.loading).toBe(false)
|
||||
expect(dialogInstance.buttonsEnabled).toBe(true)
|
||||
openSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('should open share link bundle management dialog', () => {
|
||||
const openSpy = jest.spyOn(modalService, 'open')
|
||||
component.manageShareLinkBundles()
|
||||
expect(openSpy).toHaveBeenCalledWith(
|
||||
ShareLinkBundleManageDialogComponent,
|
||||
expect.objectContaining({ backdrop: 'static', size: 'lg' })
|
||||
)
|
||||
openSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
SelectionDataItem,
|
||||
} from 'src/app/services/rest/document.service'
|
||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||
import { ShareLinkBundleService } from 'src/app/services/rest/share-link-bundle.service'
|
||||
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { TagService } from 'src/app/services/rest/tag.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
@@ -54,6 +55,8 @@ import {
|
||||
} from '../../common/filterable-dropdown/filterable-dropdown.component'
|
||||
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
|
||||
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
|
||||
import { ShareLinkBundleDialogComponent } from '../../common/share-link-bundle-dialog/share-link-bundle-dialog.component'
|
||||
import { ShareLinkBundleManageDialogComponent } from '../../common/share-link-bundle-manage-dialog/share-link-bundle-manage-dialog.component'
|
||||
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
||||
import { CustomFieldsBulkEditDialogComponent } from './custom-fields-bulk-edit-dialog/custom-fields-bulk-edit-dialog.component'
|
||||
|
||||
@@ -87,6 +90,7 @@ export class BulkEditorComponent
|
||||
private customFieldService = inject(CustomFieldsService)
|
||||
private permissionService = inject(PermissionsService)
|
||||
private savedViewService = inject(SavedViewService)
|
||||
private shareLinkBundleService = inject(ShareLinkBundleService)
|
||||
|
||||
tagSelectionModel = new FilterableDropdownSelectionModel(true)
|
||||
correspondentSelectionModel = new FilterableDropdownSelectionModel()
|
||||
@@ -908,6 +912,62 @@ export class BulkEditorComponent
|
||||
return this.settings.get(SETTINGS_KEYS.EMAIL_ENABLED)
|
||||
}
|
||||
|
||||
createShareLinkBundle() {
|
||||
const modal = this.modalService.open(ShareLinkBundleDialogComponent, {
|
||||
backdrop: 'static',
|
||||
size: 'lg',
|
||||
})
|
||||
const dialog = modal.componentInstance as ShareLinkBundleDialogComponent
|
||||
const selectedDocuments = this.list.documents.filter((d) =>
|
||||
this.list.selected.has(d.id)
|
||||
)
|
||||
dialog.documents = selectedDocuments
|
||||
dialog.confirmClicked
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.subscribe(() => {
|
||||
const payload = dialog.payload
|
||||
if (!payload) {
|
||||
return
|
||||
}
|
||||
dialog.loading = true
|
||||
dialog.buttonsEnabled = false
|
||||
this.shareLinkBundleService
|
||||
.createBundle(payload)
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
dialog.loading = false
|
||||
dialog.buttonsEnabled = false
|
||||
dialog.createdBundle = result
|
||||
dialog.copied = false
|
||||
dialog.payload = null
|
||||
dialog.onOpenManage = () => {
|
||||
modal.close()
|
||||
this.manageShareLinkBundles()
|
||||
}
|
||||
this.toastService.showInfo(
|
||||
$localize`Share link bundle creation requested.`
|
||||
)
|
||||
},
|
||||
error: (error) => {
|
||||
dialog.loading = false
|
||||
dialog.buttonsEnabled = true
|
||||
this.toastService.showError(
|
||||
$localize`Share link bundle creation is not available yet.`,
|
||||
error
|
||||
)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
manageShareLinkBundles() {
|
||||
const modal = this.modalService.open(ShareLinkBundleManageDialogComponent, {
|
||||
backdrop: 'static',
|
||||
size: 'lg',
|
||||
})
|
||||
}
|
||||
|
||||
emailSelected() {
|
||||
const allHaveArchiveVersion = this.list.documents
|
||||
.filter((d) => this.list.selected.has(d.id))
|
||||
|
||||
46
src-ui/src/app/data/share-link-bundle.ts
Normal file
46
src-ui/src/app/data/share-link-bundle.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { FileVersion } from './share-link'
|
||||
|
||||
export enum ShareLinkBundleStatus {
|
||||
Pending = 'pending',
|
||||
Processing = 'processing',
|
||||
Ready = 'ready',
|
||||
Failed = 'failed',
|
||||
}
|
||||
|
||||
export interface ShareLinkBundleSummary {
|
||||
id: number
|
||||
slug: string
|
||||
created: string // Date
|
||||
expiration?: string // Date
|
||||
documents: number[]
|
||||
document_count: number
|
||||
file_version: FileVersion
|
||||
status: ShareLinkBundleStatus
|
||||
built_at?: string
|
||||
size_bytes?: number
|
||||
last_error?: string
|
||||
}
|
||||
|
||||
export interface ShareLinkBundleCreatePayload {
|
||||
document_ids: number[]
|
||||
file_version: FileVersion
|
||||
expiration_days: number | null
|
||||
}
|
||||
|
||||
export const SHARE_LINK_BUNDLE_STATUS_LABELS: Record<
|
||||
ShareLinkBundleStatus,
|
||||
string
|
||||
> = {
|
||||
[ShareLinkBundleStatus.Pending]: $localize`Pending`,
|
||||
[ShareLinkBundleStatus.Processing]: $localize`Processing`,
|
||||
[ShareLinkBundleStatus.Ready]: $localize`Ready`,
|
||||
[ShareLinkBundleStatus.Failed]: $localize`Failed`,
|
||||
}
|
||||
|
||||
export const SHARE_LINK_BUNDLE_FILE_VERSION_LABELS: Record<
|
||||
FileVersion,
|
||||
string
|
||||
> = {
|
||||
[FileVersion.Archive]: $localize`Archive`,
|
||||
[FileVersion.Original]: $localize`Original`,
|
||||
}
|
||||
@@ -5,6 +5,18 @@ export enum FileVersion {
|
||||
Original = 'original',
|
||||
}
|
||||
|
||||
export interface ShareLinkExpirationOption {
|
||||
label: string
|
||||
value: number | null
|
||||
}
|
||||
|
||||
export const SHARE_LINK_EXPIRATION_OPTIONS: ShareLinkExpirationOption[] = [
|
||||
{ label: $localize`1 day`, value: 1 },
|
||||
{ label: $localize`7 days`, value: 7 },
|
||||
{ label: $localize`30 days`, value: 30 },
|
||||
{ label: $localize`Never`, value: null },
|
||||
]
|
||||
|
||||
export interface ShareLink extends ObjectWithPermissions {
|
||||
created: string // Date
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { HttpTestingController } from '@angular/common/http/testing'
|
||||
import { TestBed } from '@angular/core/testing'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { environment } from 'src/environments/environment'
|
||||
import { commonAbstractPaperlessServiceTests } from './abstract-paperless-service.spec'
|
||||
import { ShareLinkBundleService } from './share-link-bundle.service'
|
||||
|
||||
const endpoint = 'share_link_bundles'
|
||||
|
||||
commonAbstractPaperlessServiceTests(endpoint, ShareLinkBundleService)
|
||||
|
||||
describe('ShareLinkBundleService', () => {
|
||||
let httpTestingController: HttpTestingController
|
||||
let service: ShareLinkBundleService
|
||||
let subscription: Subscription | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
httpTestingController = TestBed.inject(HttpTestingController)
|
||||
service = TestBed.inject(ShareLinkBundleService)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
subscription?.unsubscribe()
|
||||
httpTestingController.verify()
|
||||
})
|
||||
|
||||
it('creates bundled share links', () => {
|
||||
const payload = {
|
||||
document_ids: [1, 2],
|
||||
file_version: 'archive',
|
||||
expiration_days: 7,
|
||||
}
|
||||
subscription = service.createBundle(payload as any).subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}${endpoint}/`
|
||||
)
|
||||
expect(req.request.method).toBe('POST')
|
||||
expect(req.request.body).toEqual(payload)
|
||||
req.flush({})
|
||||
})
|
||||
|
||||
it('rebuilds bundles', () => {
|
||||
subscription = service.rebuildBundle(12).subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}${endpoint}/12/rebuild/`
|
||||
)
|
||||
expect(req.request.method).toBe('POST')
|
||||
expect(req.request.body).toEqual({})
|
||||
req.flush({})
|
||||
})
|
||||
|
||||
it('lists bundles with expected parameters', () => {
|
||||
subscription = service.listAllBundles().subscribe()
|
||||
const req = httpTestingController.expectOne(
|
||||
`${environment.apiBaseUrl}${endpoint}/?page=1&page_size=1000&ordering=-created`
|
||||
)
|
||||
expect(req.request.method).toBe('GET')
|
||||
req.flush({ results: [] })
|
||||
})
|
||||
})
|
||||
41
src-ui/src/app/services/rest/share-link-bundle.service.ts
Normal file
41
src-ui/src/app/services/rest/share-link-bundle.service.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
import {
|
||||
ShareLinkBundleCreatePayload,
|
||||
ShareLinkBundleSummary,
|
||||
} from 'src/app/data/share-link-bundle'
|
||||
import { AbstractNameFilterService } from './abstract-name-filter-service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ShareLinkBundleService extends AbstractNameFilterService<ShareLinkBundleSummary> {
|
||||
constructor() {
|
||||
super()
|
||||
this.resourceName = 'share_link_bundles'
|
||||
}
|
||||
|
||||
createBundle(
|
||||
payload: ShareLinkBundleCreatePayload
|
||||
): Observable<ShareLinkBundleSummary> {
|
||||
this.clearCache()
|
||||
return this.http.post<ShareLinkBundleSummary>(
|
||||
this.getResourceUrl(),
|
||||
payload
|
||||
)
|
||||
}
|
||||
rebuildBundle(bundleId: number): Observable<ShareLinkBundleSummary> {
|
||||
this.clearCache()
|
||||
return this.http.post<ShareLinkBundleSummary>(
|
||||
this.getResourceUrl(bundleId, 'rebuild'),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
listAllBundles(): Observable<ShareLinkBundleSummary[]> {
|
||||
return this.list(1, 1000, 'created', true).pipe(
|
||||
map((response) => response.results)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -892,7 +892,7 @@
|
||||
<context context-type="sourcefile">src/app/components/admin/logs/logs.component.html</context>
|
||||
<context context-type="linenumber">60</context>
|
||||
</context-group>
|
||||
<target state="translated">En alta git</target>
|
||||
<target state="needs-translation">Jump to bottom</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1255048712725285892" datatype="html">
|
||||
<source>Options to customize appearance, notifications and more. Settings apply to the <strong>current user only</strong>.</source>
|
||||
@@ -2212,7 +2212,7 @@
|
||||
<context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
|
||||
<context context-type="linenumber">241</context>
|
||||
</context-group>
|
||||
<target state="translated">Çöp Kutusu</target>
|
||||
<target state="translated">Çöp kutusu</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3818027200170621545" datatype="html">
|
||||
<source>Manage trashed documents that are pending deletion.</source>
|
||||
@@ -6620,7 +6620,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
</context-group>
|
||||
<target state="translated">Doğrulama uygulamanızla QR kodunu tarayın ve ardından aşağıya kodu girin</target>
|
||||
<target state="translated">Doğrulama uygulamanızla QR kodunu tarayın ve ardından aşağıdaki kodu girin</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5867169599865838267" datatype="html">
|
||||
<source>Authenticator secret</source>
|
||||
@@ -6660,7 +6660,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.html</context>
|
||||
<context context-type="linenumber">158</context>
|
||||
</context-group>
|
||||
<target state="translated">Kodları kopyala</target>
|
||||
<target state="needs-translation">Copy codes</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6141884091799403188" datatype="html">
|
||||
<source>Emails must match</source>
|
||||
@@ -6700,7 +6700,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">225</context>
|
||||
</context-group>
|
||||
<target state="translated">Yetkilendirme tokeni oluştururken bir hatayla karşılaşıldı</target>
|
||||
<target state="needs-translation">Error generating auth token</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4153637646944982460" datatype="html">
|
||||
<source>Error disconnecting social account</source>
|
||||
@@ -6708,7 +6708,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/profile-edit-dialog/profile-edit-dialog.component.ts</context>
|
||||
<context context-type="linenumber">250</context>
|
||||
</context-group>
|
||||
<target state="translated">Sosyal hesapları kaldırırken bir hata ile karşılaşıldı</target>
|
||||
<target state="needs-translation">Error disconnecting social account</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5939111172212776886" datatype="html">
|
||||
<source>Error fetching TOTP settings</source>
|
||||
@@ -6764,7 +6764,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
|
||||
<context context-type="linenumber">8,10</context>
|
||||
</context-group>
|
||||
<target state="translated"> Herhangi bir mevcut link bulunmuyor </target>
|
||||
<target state="needs-translation"> No existing links </target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7419704019640008953" datatype="html">
|
||||
<source>Share</source>
|
||||
@@ -6788,7 +6788,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/share-links-dialog/share-links-dialog.component.html</context>
|
||||
<context context-type="linenumber">52</context>
|
||||
</context-group>
|
||||
<target state="translated">Son Kullanım Tarihi</target>
|
||||
<target state="needs-translation">Expires</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4776429682428363094" datatype="html">
|
||||
<source>1 day</source>
|
||||
@@ -6964,7 +6964,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||
<context context-type="linenumber">76</context>
|
||||
</context-group>
|
||||
<target state="translated">Veritabanı İşlemleri Durumu</target>
|
||||
<target state="translated">Göç Durumu</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7489316373554112115" datatype="html">
|
||||
<source>Up to date</source>
|
||||
@@ -6980,7 +6980,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||
<context context-type="linenumber">85</context>
|
||||
</context-group>
|
||||
<target state="translated">En Son Uygulanan Veritabanı İşlemleri</target>
|
||||
<target state="needs-translation">Latest Migration</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4632965004151576238" datatype="html">
|
||||
<source>Pending Migrations</source>
|
||||
@@ -6988,7 +6988,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||
<context context-type="linenumber">87</context>
|
||||
</context-group>
|
||||
<target state="translated">Bekleyen Veritabanı İşlemleri</target>
|
||||
<target state="needs-translation">Pending Migrations</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2790343143501919450" datatype="html">
|
||||
<source>Tasks Queue</source>
|
||||
@@ -7068,7 +7068,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||
<context context-type="linenumber">218</context>
|
||||
</context-group>
|
||||
<target state="translated">Son Eğitim Tarihi</target>
|
||||
<target state="needs-translation">Last Trained</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6427836860962380759" datatype="html">
|
||||
<source>Sanity Checker</source>
|
||||
@@ -7076,7 +7076,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/common/system-status-dialog/system-status-dialog.component.html</context>
|
||||
<context context-type="linenumber">223</context>
|
||||
</context-group>
|
||||
<target state="translated">Son Geçerlilik Kontrol Edilme Tarihi</target>
|
||||
<target state="needs-translation">Sanity Checker</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6578747070254776938" datatype="html">
|
||||
<source>Last Run</source>
|
||||
@@ -7188,7 +7188,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">363</context>
|
||||
</context-group>
|
||||
<target state="translated">Belge türüne göre filtrele</target>
|
||||
<target state="needs-translation">Filter by document type</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="157572966557284263" datatype="html">
|
||||
<source>Filter by storage path</source>
|
||||
@@ -7204,7 +7204,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">370</context>
|
||||
</context-group>
|
||||
<target state="translated">Depolama dizinine göre filtrele</target>
|
||||
<target state="needs-translation">Filter by storage path</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="883965278435032344" datatype="html">
|
||||
<source>Filter by owner</source>
|
||||
@@ -7446,7 +7446,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
<context context-type="linenumber">11</context>
|
||||
</context-group>
|
||||
<target state="translated">-</target>
|
||||
<target state="needs-translation">-</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8479257185772414452" datatype="html">
|
||||
<source>+</source>
|
||||
@@ -7454,7 +7454,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
</context-group>
|
||||
<target state="translated">+</target>
|
||||
<target state="needs-translation">+</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8659635229098859487" datatype="html">
|
||||
<source>Download original</source>
|
||||
@@ -7474,7 +7474,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">91</context>
|
||||
</context-group>
|
||||
<target state="translated">Yeniden İşle</target>
|
||||
<target state="needs-translation">Reprocess</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7049887240439736400" datatype="html">
|
||||
<source>Print</source>
|
||||
@@ -7826,7 +7826,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">416,418</context>
|
||||
</context-group>
|
||||
<target state="translated">İçerik yüklenirken bir hata ile karşılaşıldı: <x id="PH" equiv-text="err.message ?? err.toString()"/></target>
|
||||
<target state="needs-translation">An error occurred loading content: <x id="PH" equiv-text="err.message ?? err.toString()"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3200733026060976258" datatype="html">
|
||||
<source>Document changes detected</source>
|
||||
@@ -7842,7 +7842,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">451</context>
|
||||
</context-group>
|
||||
<target state="translated">Tarayıcı oturumunuzdaki bu belgenin sürümü, mevcut sürümden daha eski görünüyor.</target>
|
||||
<target state="needs-translation">The version of this document in your browser session appears older than the existing version.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="237142428785956348" datatype="html">
|
||||
<source>Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</source>
|
||||
@@ -7850,7 +7850,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">452</context>
|
||||
</context-group>
|
||||
<target state="translated">Belgeyi buraya kaydetmek, yapılan diğer değişikliklerin üzerine yazabilir. Mevcut sürümü geri yüklemek için değişikliklerinizi geri alın veya belgeyi kapatın.</target>
|
||||
<target state="needs-translation">Saving the document here may overwrite other changes that were made. To restore the existing version, discard your changes or close the document.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8720977247725652816" datatype="html">
|
||||
<source>Ok</source>
|
||||
@@ -7938,7 +7938,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">900</context>
|
||||
</context-group>
|
||||
<target state="translated">Belgeyi kaydederken bir hata ile karşılaşıldı "<x id="PH" equiv-text="this.document.title"/>"</target>
|
||||
<target state="needs-translation">Error saving document "<x id="PH" equiv-text="this.document.title"/>"</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="448882439049417053" datatype="html">
|
||||
<source>Error saving document</source>
|
||||
@@ -7954,7 +7954,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">982</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="PH" equiv-text="this.document.title"/> belgesini gerçekten çöp kutusuna taşımak istiyor musunuz?</target>
|
||||
<target state="needs-translation">Do you really want to move the document "<x id="PH" equiv-text="this.document.title"/>" to the trash?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="282586936710748252" datatype="html">
|
||||
<source>Documents can be restored prior to permanent deletion.</source>
|
||||
@@ -8046,7 +8046,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">1173</context>
|
||||
</context-group>
|
||||
<target state="translated">Sayfa Sığdır</target>
|
||||
<target state="needs-translation">Page Fit</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4663705961777238777" datatype="html">
|
||||
<source>PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</source>
|
||||
@@ -8054,7 +8054,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">1411</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="this.document.title"/>" için PDF düzenleme işlemleri arka planda başlayacak.</target>
|
||||
<target state="needs-translation">PDF edit operation for "<x id="PH" equiv-text="this.document.title"/>" will begin in the background.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="9043972994040261999" datatype="html">
|
||||
<source>Error executing PDF edit operation</source>
|
||||
@@ -8062,7 +8062,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">1423</context>
|
||||
</context-group>
|
||||
<target state="translated">PDF düzenleme işlemleri uygulanırken bir hata ile karşılaşıldı</target>
|
||||
<target state="needs-translation">Error executing PDF edit operation</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3740891324955700797" datatype="html">
|
||||
<source>Print failed.</source>
|
||||
@@ -8078,7 +8078,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.ts</context>
|
||||
<context context-type="linenumber">1472</context>
|
||||
</context-group>
|
||||
<target state="translated">Belgeyi yazdırmaya hazırlarken bir hata ile karşılaşıldı.</target>
|
||||
<target state="needs-translation">Error loading document for printing.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6085793215710522488" datatype="html">
|
||||
<source>An error occurred loading tiff: <x id="PH" equiv-text="err.toString()"/></source>
|
||||
@@ -8178,7 +8178,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
</context-group>
|
||||
<target state="translated">Özel alanları filtrele</target>
|
||||
<target state="needs-translation">Filter custom fields</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5139192806922838657" datatype="html">
|
||||
<source>Set values</source>
|
||||
@@ -8186,7 +8186,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
</context-group>
|
||||
<target state="translated">Değerleri ayarla</target>
|
||||
<target state="needs-translation">Set values</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1050269006235116171" datatype="html">
|
||||
<source>Rotate</source>
|
||||
@@ -8242,7 +8242,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">290</context>
|
||||
</context-group>
|
||||
<target state="translated">Toplu işlemler çalıştırılırken bir hata ile karşılaşıldı</target>
|
||||
<target state="needs-translation">Error executing bulk operation</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7894972847287473517" datatype="html">
|
||||
<source>"<x id="PH" equiv-text="items[0].name"/>"</source>
|
||||
@@ -8280,7 +8280,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">405</context>
|
||||
</context-group>
|
||||
<target state="translated">Etiket atanmasını onaylayın</target>
|
||||
<target state="translated">Etiket atanmayi doğrulayın</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6619516195038467207" datatype="html">
|
||||
<source>This operation will add the tag "<x id="PH" equiv-text="tag.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
@@ -8296,7 +8296,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">416,418</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu işlem <x id="PH" equiv-text="this._localizeList( changedTags.itemsToAdd )"/> etiketlerini <x id="PH_1" equiv-text="this.list.selected.size"/> seçili belge veya belgelere ekleyecektir.</target>
|
||||
<target state="needs-translation">This operation will add the tags <x id="PH" equiv-text="this._localizeList( changedTags.itemsToAdd )"/> to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7181166515756808573" datatype="html">
|
||||
<source>This operation will remove the tag "<x id="PH" equiv-text="tag.name"/>" from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
@@ -8312,7 +8312,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">429,431</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu işlem <x id="PH" equiv-text="this._localizeList( changedTags.itemsToRemove )"/> etiketlerini <x id="PH_1" equiv-text="this.list.selected.size"/> seçili belge veya belgelerden kaldıracaktır.</target>
|
||||
<target state="needs-translation">This operation will remove the tags <x id="PH" equiv-text="this._localizeList( changedTags.itemsToRemove )"/> from <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2739066218579571288" datatype="html">
|
||||
<source>This operation will add the tags <x id="PH" equiv-text="this._localizeList( changedTags.itemsToAdd )"/> and remove the tags <x id="PH_1" equiv-text="this._localizeList( changedTags.itemsToRemove )"/> on <x id="PH_2" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
@@ -8376,7 +8376,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">550</context>
|
||||
</context-group>
|
||||
<target state="translated">Depolama dizinlerini atamayı onaylayın</target>
|
||||
<target state="needs-translation">Confirm storage path assignment</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8750527458618415924" datatype="html">
|
||||
<source>This operation will assign the storage path "<x id="PH" equiv-text="storagePath.name"/>" to <x id="PH_1" equiv-text="this.list.selected.size"/> selected document(s).</source>
|
||||
@@ -8472,7 +8472,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">828</context>
|
||||
</context-group>
|
||||
<target state="translated">Döndürmeyi onaylayın</target>
|
||||
<target state="needs-translation">Rotate confirm</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6390006284731990222" datatype="html">
|
||||
<source>This operation will permanently rotate the original version of <x id="PH" equiv-text="this.list.selected.size"/> document(s).</source>
|
||||
@@ -8480,7 +8480,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">829</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu işlem <x id="PH" equiv-text="this.list.selected.size"/> belgede, orijinal sürümündeki sayfaları kalıcı olarak döndürecektir.</target>
|
||||
<target state="needs-translation">This operation will permanently rotate the original version of <x id="PH" equiv-text="this.list.selected.size"/> document(s).</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7910756456450124185" datatype="html">
|
||||
<source>Merge confirm</source>
|
||||
@@ -8488,7 +8488,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">848</context>
|
||||
</context-group>
|
||||
<target state="translated">Birleştirmeyi onaylayın</target>
|
||||
<target state="needs-translation">Merge confirm</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7643543647233874431" datatype="html">
|
||||
<source>This operation will merge <x id="PH" equiv-text="this.list.selected.size"/> selected documents into a new document.</source>
|
||||
@@ -8496,7 +8496,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
|
||||
<context context-type="linenumber">849</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu işlem <x id="PH" equiv-text="this.list.selected.size"/> belgeyi yeni bir belgede birleştirecektir.</target>
|
||||
<target state="needs-translation">This operation will merge <x id="PH" equiv-text="this.list.selected.size"/> selected documents into a new document.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7869008840945899895" datatype="html">
|
||||
<source>Merged document will be queued for consumption.</source>
|
||||
@@ -8536,7 +8536,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/bulk-editor/custom-fields-bulk-edit-dialog/custom-fields-bulk-edit-dialog.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
<target state="translated">Özel alanları seçin</target>
|
||||
<target state="needs-translation">Select custom fields</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8244572554104037643" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =1 {This operation will also remove 1 custom field from the selected documents.} other {This operation will also
|
||||
@@ -8581,7 +8581,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">91,92</context>
|
||||
</context-group>
|
||||
<target state="translated">Oluşturulmuş: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></target>
|
||||
<target state="needs-translation">Created: <x id="INTERPOLATION" equiv-text="{{ document.created | customDate }}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2030261243264601523" datatype="html">
|
||||
<source>Added: <x id="INTERPOLATION" equiv-text="{{ document.added | customDate }}"/></source>
|
||||
@@ -8661,7 +8661,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
<target state="translated">Etiket filtresini aç/kapat</target>
|
||||
<target state="needs-translation">Toggle tag filter</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4648526799630820486" datatype="html">
|
||||
<source>Toggle correspondent filter</source>
|
||||
@@ -8677,7 +8677,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">59</context>
|
||||
</context-group>
|
||||
<target state="translated">Belge türü filtresini aç/kapat</target>
|
||||
<target state="needs-translation">Toggle document type filter</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8950368321707344185" datatype="html">
|
||||
<source>Toggle storage path filter</source>
|
||||
@@ -8685,7 +8685,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-card-small/document-card-small.component.html</context>
|
||||
<context context-type="linenumber">66</context>
|
||||
</context-group>
|
||||
<target state="translated">Depolama dizini filtresini aç/kapat</target>
|
||||
<target state="needs-translation">Toggle storage path filter</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3797570084942068182" datatype="html">
|
||||
<source>Select</source>
|
||||
@@ -8837,7 +8837,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">169</context>
|
||||
</context-group>
|
||||
<target state="translated">Belgeleri yüklerken bir hata ile karşılaşıldı</target>
|
||||
<target state="needs-translation">Error while loading documents</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="494022736054110363" datatype="html">
|
||||
<source>Sort by ASN</source>
|
||||
@@ -8989,7 +8989,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
|
||||
<context context-type="linenumber">297,298</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="INTERPOLATION" equiv-text="{{getDisplayCustomFieldTitle(field_id)}}"/> göre sırala</target>
|
||||
<target state="needs-translation">Sort by <x id="INTERPOLATION" equiv-text="{{getDisplayCustomFieldTitle(field_id)}}"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2179847500064178686" datatype="html">
|
||||
<source>Edit document</source>
|
||||
@@ -9053,7 +9053,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/document-list.component.ts</context>
|
||||
<context context-type="linenumber">391</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>" görünümünü kaydederken bir hata ile karşılaşıldı.</target>
|
||||
<target state="needs-translation">Failed to save view "<x id="PH" equiv-text="this.list.activeSavedViewTitle"/>".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6837554170707123455" datatype="html">
|
||||
<source>View "<x id="PH" equiv-text="savedView.name"/>" created successfully.</source>
|
||||
@@ -9157,7 +9157,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">289,293</context>
|
||||
</context-group>
|
||||
<target state="translated">Belge türü: <x id="PH" equiv-text="this.documentTypeSelectionModel.items.find( (dt) => dt.id == +rule.value )?.name"/></target>
|
||||
<target state="needs-translation">Document type: <x id="PH" equiv-text="this.documentTypeSelectionModel.items.find( (dt) => dt.id == +rule.value )?.name"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4362173610367509215" datatype="html">
|
||||
<source>Without document type</source>
|
||||
@@ -9173,7 +9173,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">301,305</context>
|
||||
</context-group>
|
||||
<target state="translated">Depolama dizini: <x id="PH" equiv-text="this.storagePathSelectionModel.items.find( (sp) => sp.id == +rule.value )?.name"/></target>
|
||||
<target state="needs-translation">Storage path: <x id="PH" equiv-text="this.storagePathSelectionModel.items.find( (sp) => sp.id == +rule.value )?.name"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1562820715074533164" datatype="html">
|
||||
<source>Without storage path</source>
|
||||
@@ -9189,7 +9189,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.ts</context>
|
||||
<context context-type="linenumber">311,313</context>
|
||||
</context-group>
|
||||
<target state="translated">Etiket: <x id="PH" equiv-text="this.tagSelectionModel.items.find((t) => t.id == +rule.value)?.name"/></target>
|
||||
<target state="needs-translation">Tag: <x id="PH" equiv-text="this.tagSelectionModel.items.find((t) => t.id == +rule.value)?.name"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6494566478302448576" datatype="html">
|
||||
<source>Without any tag</source>
|
||||
@@ -9285,7 +9285,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu görünümü kaydederken filtre kuralları ile ilgili bir hata meydana geldi</target>
|
||||
<target state="needs-translation">Filter rules error occurred while saving this view</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6438839705789707938" datatype="html">
|
||||
<source>The error returned was</source>
|
||||
@@ -9373,7 +9373,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/file-drop/file-drop.component.ts</context>
|
||||
<context context-type="linenumber">142</context>
|
||||
</context-group>
|
||||
<target state="translated">Bırakılan öğeleri okurken bir hata meydana geldi: <x id="PH" equiv-text="e.message"/></target>
|
||||
<target state="needs-translation">Failed to read dropped items: <x id="PH" equiv-text="e.message"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6316128875819022658" datatype="html">
|
||||
<source>correspondent</source>
|
||||
@@ -9413,7 +9413,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/custom-fields/custom-fields.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
</context-group>
|
||||
<target state="translated">Belgelere eklenebilecek veri alanlarını özelleştirin.</target>
|
||||
<target state="needs-translation">Customize the data fields that can be attached to documents.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8019331026479399960" datatype="html">
|
||||
<source>Add Field</source>
|
||||
@@ -9453,7 +9453,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.html</context>
|
||||
<context context-type="linenumber">117</context>
|
||||
</context-group>
|
||||
<target state="translated">Belgeleri Filtrele (<x id="INTERPOLATION" equiv-text="{{ field.document_count }}"/>)</target>
|
||||
<target state="needs-translation">Filter Documents (<x id="INTERPOLATION" equiv-text="{{ field.document_count }}"/>)</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="651372623796033489" datatype="html">
|
||||
<source>No fields defined.</source>
|
||||
@@ -9593,7 +9593,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
|
||||
<context context-type="linenumber">103</context>
|
||||
</context-group>
|
||||
<target state="translated">E-posta kuralları</target>
|
||||
<target state="needs-translation">Mail rules</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1372022816709469401" datatype="html">
|
||||
<source>Add Rule</source>
|
||||
@@ -9629,7 +9629,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
|
||||
<context context-type="linenumber">143</context>
|
||||
</context-group>
|
||||
<target state="translated">İşlenmiş Postaları Görüntüle</target>
|
||||
<target state="needs-translation">View Processed Mail</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6751234988479444294" datatype="html">
|
||||
<source>No mail rules defined.</source>
|
||||
@@ -9637,7 +9637,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.html</context>
|
||||
<context context-type="linenumber">183</context>
|
||||
</context-group>
|
||||
<target state="translated">Tanımlanmış bir posta kuralı mevcut değil.</target>
|
||||
<target state="needs-translation">No mail rules defined.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3178554336792037159" datatype="html">
|
||||
<source>Error retrieving mail accounts</source>
|
||||
@@ -9645,7 +9645,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
</context-group>
|
||||
<target state="translated">Posta hesaplarından bilgi alırken bir hata meydana geldi</target>
|
||||
<target state="needs-translation">Error retrieving mail accounts</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5241231471117657636" datatype="html">
|
||||
<source>Error retrieving mail rules</source>
|
||||
@@ -9653,7 +9653,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">127</context>
|
||||
</context-group>
|
||||
<target state="translated">Posta kurallarını yüklerken bir hata meydana geldi</target>
|
||||
<target state="needs-translation">Error retrieving mail rules</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="763945516325093575" datatype="html">
|
||||
<source>OAuth2 authentication success</source>
|
||||
@@ -9661,7 +9661,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">135</context>
|
||||
</context-group>
|
||||
<target state="translated">OAuth2 yetkilendirmesi başarılı</target>
|
||||
<target state="needs-translation">OAuth2 authentication success</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="9022978370268070156" datatype="html">
|
||||
<source>OAuth2 authentication failed, see logs for details</source>
|
||||
@@ -9669,7 +9669,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">146</context>
|
||||
</context-group>
|
||||
<target state="translated">OAuth2 yetkilendirmesi ile ilgili bir hata meydana geldi, detaylar için kayıtlara (logs) göz atın</target>
|
||||
<target state="needs-translation">OAuth2 authentication failed, see logs for details</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6327501535846658797" datatype="html">
|
||||
<source>Saved account "<x id="PH" equiv-text="newMailAccount.name"/>".</source>
|
||||
@@ -9677,7 +9677,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">170</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="newMailAccount.name"/>" hesabı kaydedildi.</target>
|
||||
<target state="needs-translation">Saved account "<x id="PH" equiv-text="newMailAccount.name"/>".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8067594003836508139" datatype="html">
|
||||
<source>Error saving account.</source>
|
||||
@@ -9685,7 +9685,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">182</context>
|
||||
</context-group>
|
||||
<target state="translated">Hesabı kaydederken bir hata ile karşılaşıldı.</target>
|
||||
<target state="needs-translation">Error saving account.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5641934153807844674" datatype="html">
|
||||
<source>Confirm delete mail account</source>
|
||||
@@ -9701,7 +9701,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">191</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu işlem, bu posta hesabını kalıcı olarak silecektir.</target>
|
||||
<target state="needs-translation">This operation will permanently delete this mail account.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5876433590301754883" datatype="html">
|
||||
<source>Deleted mail account "<x id="PH" equiv-text="account.name"/>"</source>
|
||||
@@ -9709,7 +9709,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="account.name"/>" posta hesabı silindi</target>
|
||||
<target state="needs-translation">Deleted mail account "<x id="PH" equiv-text="account.name"/>"</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5981429299543258715" datatype="html">
|
||||
<source>Error deleting mail account "<x id="PH" equiv-text="account.name"/>".</source>
|
||||
@@ -9717,7 +9717,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">212</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="account.name"/>" posta hesabı silinirken bir hata meydana geldi.</target>
|
||||
<target state="needs-translation">Error deleting mail account "<x id="PH" equiv-text="account.name"/>".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6424800796582120505" datatype="html">
|
||||
<source>Processing mail account "<x id="PH" equiv-text="account.name"/>"</source>
|
||||
@@ -9725,7 +9725,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">224</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="account.name"/>" posta hesabı işleniyor</target>
|
||||
<target state="needs-translation">Processing mail account "<x id="PH" equiv-text="account.name"/>"</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3138185874003827652" datatype="html">
|
||||
<source>Error processing mail account "<x id="PH" equiv-text="account.name"/>"</source>
|
||||
@@ -9733,7 +9733,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">229</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="account.name"/>" posta hesabı işlenirken bir hata meydana geldi</target>
|
||||
<target state="needs-translation">Error processing mail account "<x id="PH" equiv-text="account.name"/>"</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="123368655395433699" datatype="html">
|
||||
<source>Saved rule "<x id="PH" equiv-text="newMailRule.name"/>".</source>
|
||||
@@ -9741,7 +9741,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">247</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="newMailRule.name"/>" kuralı kaydedildi.</target>
|
||||
<target state="needs-translation">Saved rule "<x id="PH" equiv-text="newMailRule.name"/>".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8951124554918814321" datatype="html">
|
||||
<source>Error saving rule.</source>
|
||||
@@ -9749,7 +9749,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">258</context>
|
||||
</context-group>
|
||||
<target state="translated">Kuralı kaydedilirken bir hata meydana geldi.</target>
|
||||
<target state="needs-translation">Error saving rule.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3574401690710711341" datatype="html">
|
||||
<source>Rule "<x id="PH" equiv-text="rule.name"/>" enabled.</source>
|
||||
@@ -9757,7 +9757,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">274</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="rule.name"/>" kuralı aktifleştirildi.</target>
|
||||
<target state="needs-translation">Rule "<x id="PH" equiv-text="rule.name"/>" enabled.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7171685227222299542" datatype="html">
|
||||
<source>Rule "<x id="PH" equiv-text="rule.name"/>" disabled.</source>
|
||||
@@ -9765,7 +9765,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">275</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="rule.name"/>" kuralı devre dışı bırakıldı.</target>
|
||||
<target state="needs-translation">Rule "<x id="PH" equiv-text="rule.name"/>" disabled.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7238791203524413596" datatype="html">
|
||||
<source>Error toggling rule "<x id="PH" equiv-text="rule.name"/>".</source>
|
||||
@@ -9773,7 +9773,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">280</context>
|
||||
</context-group>
|
||||
<target state="translated">Kuralı açarken veya kapatırken bir hata meydana geldi "<x id="PH" equiv-text="rule.name"/>".</target>
|
||||
<target state="needs-translation">Error toggling rule "<x id="PH" equiv-text="rule.name"/>".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3896080636020672118" datatype="html">
|
||||
<source>Confirm delete mail rule</source>
|
||||
@@ -9781,7 +9781,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">291</context>
|
||||
</context-group>
|
||||
<target state="translated">Posta hesabı kuralının silinmesini onaylayın</target>
|
||||
<target state="needs-translation">Confirm delete mail rule</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2250372580580310337" datatype="html">
|
||||
<source>This operation will permanently delete this mail rule.</source>
|
||||
@@ -9789,7 +9789,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">292</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu işlem, bu posta kuralını kalıcı olarak silecektir.</target>
|
||||
<target state="needs-translation">This operation will permanently delete this mail rule.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4357654589451732716" datatype="html">
|
||||
<source>Deleted mail rule "<x id="PH" equiv-text="rule.name"/>"</source>
|
||||
@@ -9797,7 +9797,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">302</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="rule.name"/>" posta kuralı silindi</target>
|
||||
<target state="needs-translation">Deleted mail rule "<x id="PH" equiv-text="rule.name"/>"</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1696130068388341598" datatype="html">
|
||||
<source>Error deleting mail rule "<x id="PH" equiv-text="rule.name"/>".</source>
|
||||
@@ -9805,7 +9805,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">313</context>
|
||||
</context-group>
|
||||
<target state="translated">"<x id="PH" equiv-text="rule.name"/>" posta kuralı silinirken bir hata meydana geldi.</target>
|
||||
<target state="needs-translation">Error deleting mail rule "<x id="PH" equiv-text="rule.name"/>".</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3061362835271417984" datatype="html">
|
||||
<source>Permissions updated</source>
|
||||
@@ -9813,7 +9813,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/mail.component.ts</context>
|
||||
<context context-type="linenumber">337</context>
|
||||
</context-group>
|
||||
<target state="translated">İzinler güncellendi</target>
|
||||
<target state="needs-translation">Permissions updated</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4639647950943944112" datatype="html">
|
||||
<source>Error updating permissions</source>
|
||||
@@ -9825,7 +9825,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||
<context context-type="linenumber">349</context>
|
||||
</context-group>
|
||||
<target state="translated">İzinler güncellenirken bir hata meydana geldi</target>
|
||||
<target state="needs-translation">Error updating permissions</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3501895737484542570" datatype="html">
|
||||
<source>Processed Mail for <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/><x id="INTERPOLATION" equiv-text="{{ rule.name }}"/><x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/></source>
|
||||
@@ -9833,7 +9833,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
|
||||
<context context-type="linenumber">2</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/><x id="INTERPOLATION" equiv-text="{{ rule.name }}"/><x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> için postalar işlendi</target>
|
||||
<target state="needs-translation">Processed Mail for <x id="START_EMPHASISED_TEXT" ctype="x-em" equiv-text="<em>"/><x id="INTERPOLATION" equiv-text="{{ rule.name }}"/><x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/></target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1991019495862291373" datatype="html">
|
||||
<source>No processed email messages found.</source>
|
||||
@@ -9841,7 +9841,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
<target state="translated">İşlenmiş posta mesajı bulunamadı.</target>
|
||||
<target state="needs-translation">No processed email messages found.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8691920320483720007" datatype="html">
|
||||
<source>Received</source>
|
||||
@@ -9849,7 +9849,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
|
||||
<context context-type="linenumber">33</context>
|
||||
</context-group>
|
||||
<target state="translated">Teslim Alındı</target>
|
||||
<target state="needs-translation">Received</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4749295647449765550" datatype="html">
|
||||
<source>Processed</source>
|
||||
@@ -9857,7 +9857,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
</context-group>
|
||||
<target state="translated">İşlendi</target>
|
||||
<target state="needs-translation">Processed</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2175109571923803648" datatype="html">
|
||||
<source>Processed mail(s) deleted</source>
|
||||
@@ -9865,7 +9865,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/mail/processed-mail-dialog/processed-mail-dialog.component.ts</context>
|
||||
<context context-type="linenumber">72</context>
|
||||
</context-group>
|
||||
<target state="translated">İşlenmiş posta veya postalar silindi</target>
|
||||
<target state="needs-translation">Processed mail(s) deleted</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4010735610815226758" datatype="html">
|
||||
<source>Filter by:</source>
|
||||
@@ -9965,7 +9965,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||
<context context-type="linenumber">196</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="PH" equiv-text="this.typeName"/> başarıyla oluşturuldu.</target>
|
||||
<target state="needs-translation">Successfully created <x id="PH" equiv-text="this.typeName"/>.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3928835053823658072" datatype="html">
|
||||
<source>Error occurred while creating <x id="PH" equiv-text="this.typeName"/>.</source>
|
||||
@@ -9973,7 +9973,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/components/manage/management-list/management-list.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
</context-group>
|
||||
<target state="translated"><x id="PH" equiv-text="this.typeName"/> oluşturulurken bir hata meydana geldi.</target>
|
||||
<target state="needs-translation">Error occurred while creating <x id="PH" equiv-text="this.typeName"/>.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4835942264662718903" datatype="html">
|
||||
<source>Successfully updated <x id="PH" equiv-text="this.typeName"/> "<x id="PH_1" equiv-text="object.name"/>".</source>
|
||||
@@ -10574,7 +10574,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">76</context>
|
||||
</context-group>
|
||||
<target state="translated">Çıktı Türü</target>
|
||||
<target state="needs-translation">Output Type</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2826581353496868063" datatype="html">
|
||||
<source>Language</source>
|
||||
@@ -10582,7 +10582,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">84</context>
|
||||
</context-group>
|
||||
<target state="translated">Dil</target>
|
||||
<target state="needs-translation">Language</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1713271461473302108" datatype="html">
|
||||
<source>Mode</source>
|
||||
@@ -10590,7 +10590,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">98</context>
|
||||
</context-group>
|
||||
<target state="translated">Mod</target>
|
||||
<target state="needs-translation">Mode</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6114528299376689399" datatype="html">
|
||||
<source>Skip Archive File</source>
|
||||
@@ -10598,7 +10598,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">106</context>
|
||||
</context-group>
|
||||
<target state="translated">Arşiv Dosyasını Atla</target>
|
||||
<target state="needs-translation">Skip Archive File</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="1115402553541327390" datatype="html">
|
||||
<source>Image DPI</source>
|
||||
@@ -10606,7 +10606,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">114</context>
|
||||
</context-group>
|
||||
<target state="translated">Görüntü DPI</target>
|
||||
<target state="needs-translation">Image DPI</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6352596107300820129" datatype="html">
|
||||
<source>Clean</source>
|
||||
@@ -10614,7 +10614,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">121</context>
|
||||
</context-group>
|
||||
<target state="translated">Sıfırdan İşle</target>
|
||||
<target state="needs-translation">Clean</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="725308589819024010" datatype="html">
|
||||
<source>Deskew</source>
|
||||
@@ -10622,7 +10622,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">129</context>
|
||||
</context-group>
|
||||
<target state="translated">Deskew</target>
|
||||
<target state="needs-translation">Deskew</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="6256076128297775802" datatype="html">
|
||||
<source>Rotate Pages</source>
|
||||
@@ -10630,7 +10630,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">136</context>
|
||||
</context-group>
|
||||
<target state="translated">Sayfaları Döndür</target>
|
||||
<target state="needs-translation">Rotate Pages</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8527188778859256947" datatype="html">
|
||||
<source>Rotate Pages Threshold</source>
|
||||
@@ -10638,7 +10638,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">143</context>
|
||||
</context-group>
|
||||
<target state="translated">Sayfalar Döndürmek İçin Gerekli Eşik Değeri</target>
|
||||
<target state="needs-translation">Rotate Pages Threshold</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3762131309176747817" datatype="html">
|
||||
<source>Max Image Pixels</source>
|
||||
@@ -10750,7 +10750,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">242</context>
|
||||
</context-group>
|
||||
<target state="translated">Maksimum Sayfa Sayısı</target>
|
||||
<target state="needs-translation">Max Pages</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7410804727457548947" datatype="html">
|
||||
<source>Enable Tag Detection</source>
|
||||
@@ -10758,7 +10758,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">249</context>
|
||||
</context-group>
|
||||
<target state="translated">Etiket Algılamayı Etkinleştir</target>
|
||||
<target state="needs-translation">Enable Tag Detection</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3723784143052004117" datatype="html">
|
||||
<source>Tag Mapping</source>
|
||||
@@ -10766,7 +10766,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/data/paperless-config.ts</context>
|
||||
<context context-type="linenumber">256</context>
|
||||
</context-group>
|
||||
<target state="translated">Etiket Eşlemesi</target>
|
||||
<target state="needs-translation">Tag Mapping</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5948496158474272829" datatype="html">
|
||||
<source>Warning: You have unsaved changes to your document(s).</source>
|
||||
@@ -10774,7 +10774,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/guards/dirty-doc.guard.ts</context>
|
||||
<context context-type="linenumber">16</context>
|
||||
</context-group>
|
||||
<target state="translated">Uyarı: Belge(ler)inizde kaydedilmemiş değişiklikler var.</target>
|
||||
<target state="needs-translation">Warning: You have unsaved changes to your document(s).</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="159901853873315050" datatype="html">
|
||||
<source>Unsaved Changes</source>
|
||||
@@ -10830,7 +10830,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
</context-group>
|
||||
<target state="translated">Kaydedilmiş görünümde kaydedilmemiş değişiklikleriniz var</target>
|
||||
<target state="needs-translation">You have unsaved changes to the saved view</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7282050913165342352" datatype="html">
|
||||
<source>Are you sure you want to close this saved view?</source>
|
||||
@@ -10838,7 +10838,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context>
|
||||
<context context-type="linenumber">33</context>
|
||||
</context-group>
|
||||
<target state="translated">Bu kaydedilmiş görünümü kapatmak istediğinizden emin misiniz?</target>
|
||||
<target state="needs-translation">Are you sure you want to close this saved view?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="856284624775342512" datatype="html">
|
||||
<source>Save and close</source>
|
||||
@@ -10846,7 +10846,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/guards/dirty-saved-view.guard.ts</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
</context-group>
|
||||
<target state="translated">Kaydet ve kapat</target>
|
||||
<target state="needs-translation">Save and close</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8311312207500500516" datatype="html">
|
||||
<source>You don't have permissions to do that</source>
|
||||
@@ -10854,7 +10854,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/guards/permissions.guard.ts</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
</context-group>
|
||||
<target state="translated">Bunu yapmak için gerekli izinlere sahip değilsiniz</target>
|
||||
<target state="needs-translation">You don't have permissions to do that</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="3566342898065860218" datatype="html">
|
||||
<source>Last year</source>
|
||||
@@ -10870,7 +10870,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||
<context context-type="linenumber">15</context>
|
||||
</context-group>
|
||||
<target state="translated">%s yıl önce</target>
|
||||
<target state="needs-translation">%s years ago</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="4463380307954693363" datatype="html">
|
||||
<source>Last month</source>
|
||||
@@ -10886,7 +10886,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
<target state="translated">%s ay önce</target>
|
||||
<target state="needs-translation">%s months ago</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="7591870443991978948" datatype="html">
|
||||
<source>Last week</source>
|
||||
@@ -10894,7 +10894,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
</context-group>
|
||||
<target state="translated">Geçen hafta</target>
|
||||
<target state="needs-translation">Last week</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2896962543647781653" datatype="html">
|
||||
<source>%s weeks ago</source>
|
||||
@@ -10902,7 +10902,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
</context-group>
|
||||
<target state="translated">%s hafta önce</target>
|
||||
<target state="needs-translation">%s weeks ago</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="5601594741748068208" datatype="html">
|
||||
<source>%s days ago</source>
|
||||
@@ -10910,7 +10910,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
</context-group>
|
||||
<target state="translated">%s gün önce</target>
|
||||
<target state="needs-translation">%s days ago</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="8387405724402999437" datatype="html">
|
||||
<source>%s hour ago</source>
|
||||
@@ -10918,7 +10918,7 @@ tüm <x id="CLOSE_EMPHASISED_TEXT" ctype="x-em" equiv-text="</em>"/> krite
|
||||
<context context-type="sourcefile">src/app/pipes/custom-date.pipe.ts</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
</context-group>
|
||||
<target state="translated">%s saat önce</target>
|
||||
<target state="needs-translation">%s hour ago</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="2008395012733474465" datatype="html">
|
||||
<source>%s hours ago</source>
|
||||
|
||||
@@ -13,6 +13,7 @@ from documents.models import PaperlessTask
|
||||
from documents.models import SavedView
|
||||
from documents.models import SavedViewFilterRule
|
||||
from documents.models import ShareLink
|
||||
from documents.models import ShareLinkBundle
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.tasks import update_document_parent_tags
|
||||
@@ -185,6 +186,22 @@ class ShareLinksAdmin(GuardedModelAdmin):
|
||||
return super().get_queryset(request).select_related("document__correspondent")
|
||||
|
||||
|
||||
class ShareLinkBundleAdmin(GuardedModelAdmin):
|
||||
list_display = ("created", "status", "expiration", "owner", "slug")
|
||||
list_filter = ("status", "created", "expiration", "owner")
|
||||
search_fields = ("slug",)
|
||||
|
||||
def get_queryset(self, request): # pragma: no cover
|
||||
return (
|
||||
super()
|
||||
.get_queryset(request)
|
||||
.select_related("owner")
|
||||
.prefetch_related(
|
||||
"documents",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CustomFieldsAdmin(GuardedModelAdmin):
|
||||
fields = ("name", "created", "data_type")
|
||||
readonly_fields = ("created", "data_type")
|
||||
@@ -216,6 +233,7 @@ admin.site.register(StoragePath, StoragePathAdmin)
|
||||
admin.site.register(PaperlessTask, TaskAdmin)
|
||||
admin.site.register(Note, NotesAdmin)
|
||||
admin.site.register(ShareLink, ShareLinksAdmin)
|
||||
admin.site.register(ShareLinkBundle, ShareLinkBundleAdmin)
|
||||
admin.site.register(CustomField, CustomFieldsAdmin)
|
||||
admin.site.register(CustomFieldInstance, CustomFieldInstancesAdmin)
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import PaperlessTask
|
||||
from documents.models import ShareLink
|
||||
from documents.models import ShareLinkBundle
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
|
||||
@@ -796,6 +797,29 @@ class ShareLinkFilterSet(FilterSet):
|
||||
}
|
||||
|
||||
|
||||
class ShareLinkBundleFilterSet(FilterSet):
|
||||
documents = Filter(method="filter_documents")
|
||||
|
||||
class Meta:
|
||||
model = ShareLinkBundle
|
||||
fields = {
|
||||
"created": DATETIME_KWARGS,
|
||||
"expiration": DATETIME_KWARGS,
|
||||
"status": ["exact"],
|
||||
}
|
||||
|
||||
def filter_documents(self, queryset, name, value):
|
||||
if not value:
|
||||
return queryset
|
||||
try:
|
||||
ids = [int(item) for item in value.split(",") if item]
|
||||
except ValueError:
|
||||
return queryset.none()
|
||||
if not ids:
|
||||
return queryset
|
||||
return queryset.filter(documents__in=ids).distinct()
|
||||
|
||||
|
||||
class PaperlessTaskFilterSet(FilterSet):
|
||||
acknowledged = BooleanFilter(
|
||||
label="Acknowledged",
|
||||
|
||||
190
src/documents/migrations/1075_sharelinkbundle.py
Normal file
190
src/documents/migrations/1075_sharelinkbundle.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-04 18:34
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.management import create_permissions
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
def grant_share_link_bundle_permissions(apps, schema_editor):
|
||||
# Ensure newly introduced permissions are created for all apps
|
||||
for app_config in apps.get_app_configs():
|
||||
app_config.models_module = True
|
||||
create_permissions(app_config, apps=apps, verbosity=0)
|
||||
app_config.models_module = None
|
||||
|
||||
add_document_perm = Permission.objects.filter(codename="add_document").first()
|
||||
if add_document_perm is None:
|
||||
return
|
||||
|
||||
share_bundle_permissions = Permission.objects.filter(
|
||||
codename__contains="sharelinkbundle",
|
||||
)
|
||||
|
||||
users = User.objects.filter(user_permissions=add_document_perm).distinct()
|
||||
for user in users:
|
||||
user.user_permissions.add(*share_bundle_permissions)
|
||||
|
||||
groups = Group.objects.filter(permissions=add_document_perm).distinct()
|
||||
for group in groups:
|
||||
group.permissions.add(*share_bundle_permissions)
|
||||
|
||||
|
||||
def revoke_share_link_bundle_permissions(apps, schema_editor):
|
||||
share_bundle_permissions = Permission.objects.filter(
|
||||
codename__contains="sharelinkbundle",
|
||||
)
|
||||
for user in User.objects.all():
|
||||
user.user_permissions.remove(*share_bundle_permissions)
|
||||
for group in Group.objects.all():
|
||||
group.permissions.remove(*share_bundle_permissions)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("documents", "1074_workflowrun_deleted_at_workflowrun_restored_at_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="ShareLinkBundle",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"created",
|
||||
models.DateTimeField(
|
||||
blank=True,
|
||||
db_index=True,
|
||||
default=django.utils.timezone.now,
|
||||
editable=False,
|
||||
verbose_name="created",
|
||||
),
|
||||
),
|
||||
(
|
||||
"expiration",
|
||||
models.DateTimeField(
|
||||
blank=True,
|
||||
db_index=True,
|
||||
null=True,
|
||||
verbose_name="expiration",
|
||||
),
|
||||
),
|
||||
(
|
||||
"slug",
|
||||
models.SlugField(
|
||||
blank=True,
|
||||
editable=False,
|
||||
unique=True,
|
||||
verbose_name="slug",
|
||||
),
|
||||
),
|
||||
(
|
||||
"file_version",
|
||||
models.CharField(
|
||||
choices=[("archive", "Archive"), ("original", "Original")],
|
||||
default="archive",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "Pending"),
|
||||
("processing", "Processing"),
|
||||
("ready", "Ready"),
|
||||
("failed", "Failed"),
|
||||
],
|
||||
default="pending",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
(
|
||||
"size_bytes",
|
||||
models.BigIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="size (bytes)",
|
||||
),
|
||||
),
|
||||
(
|
||||
"last_error",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
verbose_name="last error",
|
||||
),
|
||||
),
|
||||
(
|
||||
"file_path",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
max_length=512,
|
||||
verbose_name="file path",
|
||||
),
|
||||
),
|
||||
(
|
||||
"built_at",
|
||||
models.DateTimeField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="built at",
|
||||
),
|
||||
),
|
||||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="share_link_bundles",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="owner",
|
||||
),
|
||||
),
|
||||
(
|
||||
"deleted_at",
|
||||
models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
(
|
||||
"restored_at",
|
||||
models.DateTimeField(blank=True, null=True),
|
||||
),
|
||||
(
|
||||
"transaction_id",
|
||||
models.UUIDField(blank=True, null=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ("-created",),
|
||||
"verbose_name": "share link bundle",
|
||||
"verbose_name_plural": "share link bundles",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="sharelinkbundle",
|
||||
name="documents",
|
||||
field=models.ManyToManyField(
|
||||
related_name="share_link_bundles",
|
||||
to="documents.document",
|
||||
verbose_name="documents",
|
||||
),
|
||||
),
|
||||
migrations.RunPython(
|
||||
grant_share_link_bundle_permissions,
|
||||
reverse_code=revoke_share_link_bundle_permissions,
|
||||
),
|
||||
]
|
||||
@@ -777,6 +777,120 @@ class ShareLink(SoftDeleteModel):
|
||||
return f"Share Link for {self.document.title}"
|
||||
|
||||
|
||||
class ShareLinkBundle(SoftDeleteModel):
|
||||
class Status(models.TextChoices):
|
||||
PENDING = ("pending", _("Pending"))
|
||||
PROCESSING = ("processing", _("Processing"))
|
||||
READY = ("ready", _("Ready"))
|
||||
FAILED = ("failed", _("Failed"))
|
||||
|
||||
class Meta:
|
||||
ordering = ("-created",)
|
||||
verbose_name = _("share link bundle")
|
||||
verbose_name_plural = _("share link bundles")
|
||||
|
||||
created = models.DateTimeField(
|
||||
_("created"),
|
||||
default=timezone.now,
|
||||
db_index=True,
|
||||
blank=True,
|
||||
editable=False,
|
||||
)
|
||||
|
||||
expiration = models.DateTimeField(
|
||||
_("expiration"),
|
||||
blank=True,
|
||||
null=True,
|
||||
db_index=True,
|
||||
)
|
||||
|
||||
slug = models.SlugField(
|
||||
_("slug"),
|
||||
db_index=True,
|
||||
unique=True,
|
||||
blank=True,
|
||||
editable=False,
|
||||
)
|
||||
|
||||
owner = models.ForeignKey(
|
||||
User,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="share_link_bundles",
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name=_("owner"),
|
||||
)
|
||||
|
||||
file_version = models.CharField(
|
||||
max_length=50,
|
||||
choices=ShareLink.FileVersion.choices,
|
||||
default=ShareLink.FileVersion.ARCHIVE,
|
||||
)
|
||||
|
||||
status = models.CharField(
|
||||
max_length=50,
|
||||
choices=Status.choices,
|
||||
default=Status.PENDING,
|
||||
)
|
||||
|
||||
size_bytes = models.BigIntegerField(
|
||||
_("size (bytes)"),
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
last_error = models.TextField(
|
||||
_("last error"),
|
||||
blank=True,
|
||||
)
|
||||
|
||||
file_path = models.CharField(
|
||||
_("file path"),
|
||||
max_length=512,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
built_at = models.DateTimeField(
|
||||
_("built at"),
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
documents = models.ManyToManyField(
|
||||
"documents.Document",
|
||||
related_name="share_link_bundles",
|
||||
verbose_name=_("documents"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return _("Share link bundle %(slug)s") % {"slug": self.slug}
|
||||
|
||||
@property
|
||||
def absolute_file_path(self) -> Path | None:
|
||||
if not self.file_path:
|
||||
return None
|
||||
file_path = Path(self.file_path)
|
||||
if not file_path.is_absolute():
|
||||
file_path = (settings.MEDIA_ROOT / file_path).resolve()
|
||||
return file_path
|
||||
|
||||
def remove_file(self):
|
||||
path = self.absolute_file_path
|
||||
if path and path.exists():
|
||||
try:
|
||||
path.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def delete(self, using=None, *, keep_parents=False):
|
||||
self.remove_file()
|
||||
return super().delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
def hard_delete(self, using=None, *, keep_parents=False):
|
||||
self.remove_file()
|
||||
return super().hard_delete(using=using, keep_parents=keep_parents)
|
||||
|
||||
|
||||
class CustomField(models.Model):
|
||||
"""
|
||||
Defines the name and type of a custom field
|
||||
|
||||
@@ -4,6 +4,7 @@ import logging
|
||||
import math
|
||||
import re
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Literal
|
||||
@@ -21,6 +22,7 @@ from django.core.validators import MaxLengthValidator
|
||||
from django.core.validators import RegexValidator
|
||||
from django.core.validators import integer_validator
|
||||
from django.db.models import Count
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils.text import slugify
|
||||
@@ -57,6 +59,7 @@ from documents.models import PaperlessTask
|
||||
from documents.models import SavedView
|
||||
from documents.models import SavedViewFilterRule
|
||||
from documents.models import ShareLink
|
||||
from documents.models import ShareLinkBundle
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import UiSettings
|
||||
@@ -2127,6 +2130,112 @@ class ShareLinkSerializer(OwnedObjectSerializer):
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class ShareLinkBundleSerializer(OwnedObjectSerializer):
|
||||
document_ids = serializers.ListField(
|
||||
child=serializers.IntegerField(min_value=1),
|
||||
allow_empty=False,
|
||||
write_only=True,
|
||||
)
|
||||
expiration_days = serializers.IntegerField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
min_value=1,
|
||||
write_only=True,
|
||||
)
|
||||
documents = serializers.PrimaryKeyRelatedField(
|
||||
many=True,
|
||||
read_only=True,
|
||||
)
|
||||
document_count = SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = ShareLinkBundle
|
||||
fields = (
|
||||
"id",
|
||||
"created",
|
||||
"expiration",
|
||||
"expiration_days",
|
||||
"slug",
|
||||
"file_version",
|
||||
"status",
|
||||
"size_bytes",
|
||||
"last_error",
|
||||
"built_at",
|
||||
"documents",
|
||||
"document_ids",
|
||||
"document_count",
|
||||
)
|
||||
read_only_fields = (
|
||||
"id",
|
||||
"created",
|
||||
"expiration",
|
||||
"slug",
|
||||
"status",
|
||||
"size_bytes",
|
||||
"last_error",
|
||||
"built_at",
|
||||
"documents",
|
||||
"document_count",
|
||||
)
|
||||
|
||||
def validate_document_ids(self, value):
|
||||
unique_ids = set(value)
|
||||
if len(unique_ids) != len(value):
|
||||
raise serializers.ValidationError(
|
||||
_("Duplicate document identifiers are not allowed."),
|
||||
)
|
||||
return value
|
||||
|
||||
def create(self, validated_data):
|
||||
document_ids = validated_data.pop("document_ids")
|
||||
expiration_days = validated_data.pop("expiration_days", None)
|
||||
documents = validated_data.pop("documents", None)
|
||||
validated_data["slug"] = get_random_string(50)
|
||||
if expiration_days:
|
||||
validated_data["expiration"] = timezone.now() + timedelta(
|
||||
days=expiration_days,
|
||||
)
|
||||
else:
|
||||
validated_data["expiration"] = None
|
||||
|
||||
share_link_bundle = super().create(validated_data)
|
||||
|
||||
if documents is None:
|
||||
documents = list(
|
||||
Document.objects.filter(pk__in=document_ids).only(
|
||||
"pk",
|
||||
),
|
||||
)
|
||||
else:
|
||||
documents = list(documents)
|
||||
|
||||
documents_by_id = {doc.pk: doc for doc in documents}
|
||||
missing = [
|
||||
str(doc_id) for doc_id in document_ids if doc_id not in documents_by_id
|
||||
]
|
||||
if missing:
|
||||
raise serializers.ValidationError(
|
||||
{
|
||||
"document_ids": _(
|
||||
"Documents not found: %(ids)s",
|
||||
)
|
||||
% {"ids": ", ".join(missing)},
|
||||
},
|
||||
)
|
||||
|
||||
ordered_documents = [documents_by_id[doc_id] for doc_id in document_ids]
|
||||
share_link_bundle.documents.set(ordered_documents)
|
||||
share_link_bundle.document_total = len(ordered_documents)
|
||||
|
||||
return share_link_bundle
|
||||
|
||||
def get_document_count(self, obj: ShareLinkBundle) -> int:
|
||||
count = getattr(obj, "document_total", None)
|
||||
if count is not None:
|
||||
return count
|
||||
return obj.documents.count()
|
||||
|
||||
|
||||
class BulkEditObjectsSerializer(SerializerWithPerms, SetPermissionsMixin):
|
||||
objects = serializers.ListField(
|
||||
required=True,
|
||||
|
||||
@@ -396,7 +396,6 @@ class CannotMoveFilesException(Exception):
|
||||
@receiver(models.signals.post_save, sender=CustomFieldInstance, weak=False)
|
||||
@receiver(models.signals.m2m_changed, sender=Document.tags.through, weak=False)
|
||||
@receiver(models.signals.post_save, sender=Document, weak=False)
|
||||
@shared_task
|
||||
def update_filename_and_move_files(
|
||||
sender,
|
||||
instance: Document | CustomFieldInstance,
|
||||
@@ -533,34 +532,43 @@ def update_filename_and_move_files(
|
||||
)
|
||||
|
||||
|
||||
# should be disabled in /src/documents/management/commands/document_importer.py handle
|
||||
@receiver(models.signals.post_save, sender=CustomField)
|
||||
def check_paths_and_prune_custom_fields(sender, instance: CustomField, **kwargs):
|
||||
@shared_task
|
||||
def process_cf_select_update(custom_field: CustomField):
|
||||
"""
|
||||
When a custom field is updated:
|
||||
Update documents tied to a select custom field:
|
||||
|
||||
1. 'Select' custom field instances get their end-user value (e.g. in file names) from the select_options in extra_data,
|
||||
which is contained in the custom field itself. So when the field is changed, we (may) need to update the file names
|
||||
of all documents that have this custom field.
|
||||
2. If a 'Select' field option was removed, we need to nullify the custom field instances that have the option.
|
||||
"""
|
||||
select_options = {
|
||||
option["id"]: option["label"]
|
||||
for option in custom_field.extra_data.get("select_options", [])
|
||||
}
|
||||
|
||||
# Clear select values that no longer exist
|
||||
custom_field.fields.exclude(
|
||||
value_select__in=select_options.keys(),
|
||||
).update(value_select=None)
|
||||
|
||||
for cf_instance in custom_field.fields.select_related("document").iterator():
|
||||
# Update the filename and move files if necessary
|
||||
update_filename_and_move_files(CustomFieldInstance, cf_instance)
|
||||
|
||||
|
||||
# should be disabled in /src/documents/management/commands/document_importer.py handle
|
||||
@receiver(models.signals.post_save, sender=CustomField)
|
||||
def check_paths_and_prune_custom_fields(sender, instance: CustomField, **kwargs):
|
||||
"""
|
||||
When a custom field is updated, check if we need to update any documents. Done async to avoid slowing down the save operation.
|
||||
"""
|
||||
if (
|
||||
instance.data_type == CustomField.FieldDataType.SELECT
|
||||
and instance.fields.count() > 0
|
||||
and instance.extra_data
|
||||
): # Only select fields, for now
|
||||
select_options = {
|
||||
option["id"]: option["label"]
|
||||
for option in instance.extra_data.get("select_options", [])
|
||||
}
|
||||
|
||||
for cf_instance in instance.fields.all():
|
||||
# Check if the current value is still a valid option
|
||||
if cf_instance.value not in select_options:
|
||||
cf_instance.value_select = None
|
||||
cf_instance.save(update_fields=["value_select"])
|
||||
|
||||
# Update the filename and move files if necessary
|
||||
update_filename_and_move_files.delay(sender, cf_instance)
|
||||
process_cf_select_update.delay(instance)
|
||||
|
||||
|
||||
@receiver(models.signals.post_delete, sender=CustomField)
|
||||
|
||||
@@ -3,7 +3,9 @@ import hashlib
|
||||
import logging
|
||||
import shutil
|
||||
import uuid
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import tqdm
|
||||
@@ -22,6 +24,8 @@ from whoosh.writing import AsyncWriter
|
||||
from documents import index
|
||||
from documents import sanity_checker
|
||||
from documents.barcodes import BarcodePlugin
|
||||
from documents.bulk_download import ArchiveOnlyStrategy
|
||||
from documents.bulk_download import OriginalsOnlyStrategy
|
||||
from documents.caching import clear_document_caches
|
||||
from documents.classifier import DocumentClassifier
|
||||
from documents.classifier import load_classifier
|
||||
@@ -39,6 +43,8 @@ from documents.models import CustomFieldInstance
|
||||
from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import PaperlessTask
|
||||
from documents.models import ShareLink
|
||||
from documents.models import ShareLinkBundle
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import Workflow
|
||||
@@ -563,3 +569,121 @@ def update_document_parent_tags(tag: Tag, new_parent: Tag) -> None:
|
||||
|
||||
if affected:
|
||||
bulk_update_documents.delay(document_ids=list(affected))
|
||||
|
||||
|
||||
@shared_task
|
||||
def build_share_link_bundle(bundle_id: int):
|
||||
try:
|
||||
bundle = (
|
||||
ShareLinkBundle.objects.filter(pk=bundle_id)
|
||||
.prefetch_related("documents")
|
||||
.get()
|
||||
)
|
||||
except ShareLinkBundle.DoesNotExist:
|
||||
logger.warning("Share link bundle %s no longer exists.", bundle_id)
|
||||
return
|
||||
|
||||
bundle.remove_file()
|
||||
bundle.status = ShareLinkBundle.Status.PROCESSING
|
||||
bundle.last_error = ""
|
||||
bundle.size_bytes = None
|
||||
bundle.built_at = None
|
||||
bundle.file_path = ""
|
||||
bundle.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"last_error",
|
||||
"size_bytes",
|
||||
"built_at",
|
||||
"file_path",
|
||||
],
|
||||
)
|
||||
|
||||
documents = list(bundle.documents.all().order_by("pk"))
|
||||
|
||||
with NamedTemporaryFile(
|
||||
dir=settings.SCRATCH_DIR,
|
||||
suffix=".zip",
|
||||
delete=False,
|
||||
) as temp_zip:
|
||||
temp_zip_path = Path(temp_zip.name)
|
||||
|
||||
try:
|
||||
strategy_class = (
|
||||
ArchiveOnlyStrategy
|
||||
if bundle.file_version == ShareLink.FileVersion.ARCHIVE
|
||||
else OriginalsOnlyStrategy
|
||||
)
|
||||
with zipfile.ZipFile(temp_zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
|
||||
strategy = strategy_class(zipf)
|
||||
for document in documents:
|
||||
strategy.add_document(document)
|
||||
|
||||
output_dir = settings.SHARE_LINK_BUNDLE_DIR
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
final_path = (output_dir / f"{bundle.slug}.zip").resolve()
|
||||
if final_path.exists():
|
||||
final_path.unlink()
|
||||
shutil.move(str(temp_zip_path), final_path)
|
||||
|
||||
try:
|
||||
bundle.file_path = str(final_path.relative_to(settings.MEDIA_ROOT))
|
||||
except ValueError:
|
||||
bundle.file_path = str(final_path)
|
||||
bundle.size_bytes = final_path.stat().st_size
|
||||
bundle.status = ShareLinkBundle.Status.READY
|
||||
bundle.built_at = timezone.now()
|
||||
bundle.last_error = ""
|
||||
bundle.save(
|
||||
update_fields=[
|
||||
"file_path",
|
||||
"size_bytes",
|
||||
"status",
|
||||
"built_at",
|
||||
"last_error",
|
||||
],
|
||||
)
|
||||
logger.info("Built share link bundle %s", bundle.pk)
|
||||
except Exception as exc:
|
||||
logger.exception(
|
||||
"Failed to build share link bundle %s: %s",
|
||||
bundle_id,
|
||||
exc,
|
||||
)
|
||||
bundle.status = ShareLinkBundle.Status.FAILED
|
||||
bundle.last_error = str(exc)
|
||||
bundle.save(update_fields=["status", "last_error"])
|
||||
try:
|
||||
temp_zip_path.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
raise
|
||||
finally:
|
||||
if temp_zip_path.exists():
|
||||
try:
|
||||
temp_zip_path.unlink()
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@shared_task
|
||||
def cleanup_expired_share_link_bundles():
|
||||
now = timezone.now()
|
||||
expired_qs = ShareLinkBundle.objects.filter(
|
||||
deleted_at__isnull=True,
|
||||
expiration__isnull=False,
|
||||
expiration__lt=now,
|
||||
)
|
||||
count = 0
|
||||
for bundle in expired_qs.iterator():
|
||||
count += 1
|
||||
try:
|
||||
bundle.hard_delete()
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Failed to delete expired share link bundle %s: %s",
|
||||
bundle.pk,
|
||||
exc,
|
||||
)
|
||||
if count:
|
||||
logger.info("Deleted %s expired share link bundle(s)", count)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import json
|
||||
from datetime import date
|
||||
from unittest import mock
|
||||
from unittest.mock import ANY
|
||||
|
||||
from django.contrib.auth.models import Permission
|
||||
@@ -276,6 +277,52 @@ class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
|
||||
doc.refresh_from_db()
|
||||
self.assertEqual(doc.custom_fields.first().value, None)
|
||||
|
||||
@mock.patch("documents.signals.handlers.process_cf_select_update.delay")
|
||||
def test_custom_field_update_offloaded_once(self, mock_delay):
|
||||
"""
|
||||
GIVEN:
|
||||
- A select custom field attached to multiple documents
|
||||
WHEN:
|
||||
- The select options are updated
|
||||
THEN:
|
||||
- The async update task is enqueued once
|
||||
"""
|
||||
cf_select = CustomField.objects.create(
|
||||
name="Select Field",
|
||||
data_type=CustomField.FieldDataType.SELECT,
|
||||
extra_data={
|
||||
"select_options": [
|
||||
{"label": "Option 1", "id": "abc-123"},
|
||||
{"label": "Option 2", "id": "def-456"},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
documents = [
|
||||
Document.objects.create(
|
||||
title="WOW",
|
||||
content="the content",
|
||||
checksum=f"{i}",
|
||||
mime_type="application/pdf",
|
||||
)
|
||||
for i in range(3)
|
||||
]
|
||||
for document in documents:
|
||||
CustomFieldInstance.objects.create(
|
||||
document=document,
|
||||
field=cf_select,
|
||||
value_select="def-456",
|
||||
)
|
||||
|
||||
cf_select.extra_data = {
|
||||
"select_options": [
|
||||
{"label": "Option 1", "id": "abc-123"},
|
||||
],
|
||||
}
|
||||
cf_select.save()
|
||||
|
||||
mock_delay.assert_called_once_with(cf_select)
|
||||
|
||||
def test_custom_field_select_old_version(self):
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
182
src/documents/tests/test_api_share_link_bundles.py
Normal file
182
src/documents/tests/test_api_share_link_bundles.py
Normal file
@@ -0,0 +1,182 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from documents.models import ShareLink
|
||||
from documents.models import ShareLinkBundle
|
||||
from documents.tasks import cleanup_expired_share_link_bundles
|
||||
from documents.tests.factories import DocumentFactory
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class ShareLinkBundleAPITests(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/share_link_bundles/"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = User.objects.create_superuser(username="bundle_admin")
|
||||
self.client.force_authenticate(self.user)
|
||||
self.document = DocumentFactory.create()
|
||||
|
||||
@mock.patch("documents.views.build_share_link_bundle.delay")
|
||||
def test_create_bundle_triggers_build_job(self, delay_mock):
|
||||
payload = {
|
||||
"document_ids": [self.document.pk],
|
||||
"file_version": ShareLink.FileVersion.ARCHIVE,
|
||||
"expiration_days": 7,
|
||||
}
|
||||
|
||||
response = self.client.post(self.ENDPOINT, payload, format="json")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
bundle = ShareLinkBundle.objects.get(pk=response.data["id"])
|
||||
self.assertEqual(bundle.documents.count(), 1)
|
||||
self.assertEqual(bundle.status, ShareLinkBundle.Status.PENDING)
|
||||
delay_mock.assert_called_once_with(bundle.pk)
|
||||
|
||||
def test_create_bundle_rejects_missing_documents(self):
|
||||
payload = {
|
||||
"document_ids": [9999],
|
||||
"file_version": ShareLink.FileVersion.ARCHIVE,
|
||||
"expiration_days": 7,
|
||||
}
|
||||
|
||||
response = self.client.post(self.ENDPOINT, payload, format="json")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("document_ids", response.data)
|
||||
|
||||
@mock.patch("documents.views.build_share_link_bundle.delay")
|
||||
def test_rebuild_bundle_resets_state(self, delay_mock):
|
||||
bundle = ShareLinkBundle.objects.create(
|
||||
slug="rebuild-slug",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.FAILED,
|
||||
)
|
||||
bundle.documents.set([self.document])
|
||||
bundle.last_error = "Something went wrong"
|
||||
bundle.size_bytes = 100
|
||||
bundle.file_path = "path/to/file.zip"
|
||||
bundle.save()
|
||||
|
||||
response = self.client.post(f"{self.ENDPOINT}{bundle.pk}/rebuild/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
bundle.refresh_from_db()
|
||||
self.assertEqual(bundle.status, ShareLinkBundle.Status.PENDING)
|
||||
self.assertEqual(bundle.last_error, "")
|
||||
self.assertIsNone(bundle.size_bytes)
|
||||
self.assertEqual(bundle.file_path, "")
|
||||
delay_mock.assert_called_once_with(bundle.pk)
|
||||
|
||||
def test_rebuild_bundle_rejects_processing_status(self):
|
||||
bundle = ShareLinkBundle.objects.create(
|
||||
slug="processing-slug",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.PROCESSING,
|
||||
)
|
||||
bundle.documents.set([self.document])
|
||||
|
||||
response = self.client.post(f"{self.ENDPOINT}{bundle.pk}/rebuild/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("detail", response.data)
|
||||
|
||||
def test_download_ready_bundle_streams_file(self):
|
||||
bundle_file = Path(self.dirs.media_dir) / "bundles" / "ready.zip"
|
||||
bundle_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
bundle_file.write_bytes(b"binary-zip-content")
|
||||
|
||||
bundle = ShareLinkBundle.objects.create(
|
||||
slug="readyslug",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.READY,
|
||||
file_path=str(bundle_file),
|
||||
)
|
||||
bundle.documents.set([self.document])
|
||||
|
||||
self.client.logout()
|
||||
response = self.client.get(f"/share/{bundle.slug}/")
|
||||
content = b"".join(response.streaming_content)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response["Content-Type"], "application/zip")
|
||||
self.assertEqual(content, b"binary-zip-content")
|
||||
self.assertIn("attachment;", response["Content-Disposition"])
|
||||
|
||||
def test_download_pending_bundle_returns_202(self):
|
||||
bundle = ShareLinkBundle.objects.create(
|
||||
slug="pendingslug",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.PENDING,
|
||||
)
|
||||
bundle.documents.set([self.document])
|
||||
|
||||
self.client.logout()
|
||||
response = self.client.get(f"/share/{bundle.slug}/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
|
||||
@mock.patch("documents.views.build_share_link_bundle.delay")
|
||||
def test_download_missing_file_triggers_rebuild(self, delay_mock):
|
||||
bundle = ShareLinkBundle.objects.create(
|
||||
slug="missingfileslug",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.READY,
|
||||
file_path=str(Path(self.dirs.media_dir) / "does-not-exist.zip"),
|
||||
)
|
||||
bundle.documents.set([self.document])
|
||||
|
||||
self.client.logout()
|
||||
response = self.client.get(f"/share/{bundle.slug}/")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)
|
||||
bundle.refresh_from_db()
|
||||
self.assertEqual(bundle.status, ShareLinkBundle.Status.PENDING)
|
||||
delay_mock.assert_called_once_with(bundle.pk)
|
||||
|
||||
|
||||
class ShareLinkBundleTaskTests(DirectoriesMixin, APITestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.document = DocumentFactory.create()
|
||||
|
||||
def test_cleanup_expired_share_link_bundles(self):
|
||||
expired_path = Path(self.dirs.media_dir) / "expired.zip"
|
||||
expired_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
expired_path.write_bytes(b"expired")
|
||||
|
||||
active_path = Path(self.dirs.media_dir) / "active.zip"
|
||||
active_path.write_bytes(b"active")
|
||||
|
||||
expired_bundle = ShareLinkBundle.objects.create(
|
||||
slug="expired-bundle",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.READY,
|
||||
expiration=timezone.now() - timedelta(days=1),
|
||||
file_path=str(expired_path),
|
||||
)
|
||||
expired_bundle.documents.set([self.document])
|
||||
|
||||
active_bundle = ShareLinkBundle.objects.create(
|
||||
slug="active-bundle",
|
||||
file_version=ShareLink.FileVersion.ARCHIVE,
|
||||
status=ShareLinkBundle.Status.READY,
|
||||
expiration=timezone.now() + timedelta(days=1),
|
||||
file_path=str(active_path),
|
||||
)
|
||||
active_bundle.documents.set([self.document])
|
||||
|
||||
cleanup_expired_share_link_bundles()
|
||||
|
||||
self.assertFalse(ShareLinkBundle.objects.filter(pk=expired_bundle.pk).exists())
|
||||
self.assertTrue(ShareLinkBundle.objects.filter(pk=active_bundle.pk).exists())
|
||||
self.assertFalse(expired_path.exists())
|
||||
self.assertTrue(active_path.exists())
|
||||
@@ -530,6 +530,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
|
||||
@override_settings(
|
||||
FILENAME_FORMAT="{{title}}_{{custom_fields|get_cf_value('test')}}",
|
||||
CELERY_TASK_ALWAYS_EAGER=True,
|
||||
)
|
||||
@mock.patch("documents.signals.handlers.update_filename_and_move_files")
|
||||
def test_select_cf_updated(self, m):
|
||||
@@ -569,7 +570,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
self.assertEqual(generate_filename(doc), Path("document_apple.pdf"))
|
||||
|
||||
# handler should not have been called
|
||||
self.assertEqual(m.delay.call_count, 0)
|
||||
self.assertEqual(m.call_count, 0)
|
||||
cf.extra_data = {
|
||||
"select_options": [
|
||||
{"label": "aubergine", "id": "abc123"},
|
||||
@@ -579,8 +580,8 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
}
|
||||
cf.save()
|
||||
self.assertEqual(generate_filename(doc), Path("document_aubergine.pdf"))
|
||||
# handler should have been called via delay
|
||||
self.assertEqual(m.delay.call_count, 1)
|
||||
# handler should have been called once via the async task
|
||||
self.assertEqual(m.call_count, 1)
|
||||
|
||||
|
||||
class TestFileHandlingWithArchive(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
|
||||
|
||||
@@ -50,6 +50,7 @@ from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import get_language
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.http import condition
|
||||
@@ -69,6 +70,7 @@ from packaging import version as packaging_version
|
||||
from redis import Redis
|
||||
from rest_framework import parsers
|
||||
from rest_framework import serializers
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import NotFound
|
||||
from rest_framework.exceptions import ValidationError
|
||||
@@ -117,6 +119,7 @@ from documents.filters import DocumentTypeFilterSet
|
||||
from documents.filters import ObjectOwnedOrGrantedPermissionsFilter
|
||||
from documents.filters import ObjectOwnedPermissionsFilter
|
||||
from documents.filters import PaperlessTaskFilterSet
|
||||
from documents.filters import ShareLinkBundleFilterSet
|
||||
from documents.filters import ShareLinkFilterSet
|
||||
from documents.filters import StoragePathFilterSet
|
||||
from documents.filters import TagFilterSet
|
||||
@@ -134,6 +137,7 @@ from documents.models import Note
|
||||
from documents.models import PaperlessTask
|
||||
from documents.models import SavedView
|
||||
from documents.models import ShareLink
|
||||
from documents.models import ShareLinkBundle
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.models import UiSettings
|
||||
@@ -167,6 +171,7 @@ from documents.serialisers import PostDocumentSerializer
|
||||
from documents.serialisers import RunTaskViewSerializer
|
||||
from documents.serialisers import SavedViewSerializer
|
||||
from documents.serialisers import SearchResultSerializer
|
||||
from documents.serialisers import ShareLinkBundleSerializer
|
||||
from documents.serialisers import ShareLinkSerializer
|
||||
from documents.serialisers import StoragePathSerializer
|
||||
from documents.serialisers import StoragePathTestSerializer
|
||||
@@ -179,6 +184,7 @@ from documents.serialisers import WorkflowActionSerializer
|
||||
from documents.serialisers import WorkflowSerializer
|
||||
from documents.serialisers import WorkflowTriggerSerializer
|
||||
from documents.signals import document_updated
|
||||
from documents.tasks import build_share_link_bundle
|
||||
from documents.tasks import consume_file
|
||||
from documents.tasks import empty_trash
|
||||
from documents.tasks import index_optimize
|
||||
@@ -2268,7 +2274,7 @@ class BulkDownloadView(GenericAPIView):
|
||||
follow_filename_format = serializer.validated_data.get("follow_formatting")
|
||||
|
||||
for document in documents:
|
||||
if not has_perms_owner_aware(request.user, "view_document", document):
|
||||
if not has_perms_owner_aware(request.user, "change_document", document):
|
||||
return HttpResponseForbidden("Insufficient permissions")
|
||||
|
||||
settings.SCRATCH_DIR.mkdir(parents=True, exist_ok=True)
|
||||
@@ -2615,21 +2621,225 @@ class ShareLinkViewSet(ModelViewSet, PassUserMixin):
|
||||
ordering_fields = ("created", "expiration", "document")
|
||||
|
||||
|
||||
class ShareLinkBundleViewSet(ModelViewSet, PassUserMixin):
|
||||
model = ShareLinkBundle
|
||||
|
||||
queryset = ShareLinkBundle.objects.all()
|
||||
|
||||
serializer_class = ShareLinkBundleSerializer
|
||||
pagination_class = StandardPagination
|
||||
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
|
||||
filter_backends = (
|
||||
DjangoFilterBackend,
|
||||
OrderingFilter,
|
||||
ObjectOwnedOrGrantedPermissionsFilter,
|
||||
)
|
||||
filterset_class = ShareLinkBundleFilterSet
|
||||
ordering_fields = ("created", "expiration", "status")
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.prefetch_related("documents")
|
||||
.annotate(document_total=Count("documents", distinct=True))
|
||||
)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
document_ids = serializer.validated_data["document_ids"]
|
||||
documents_qs = Document.objects.filter(pk__in=document_ids).select_related(
|
||||
"owner",
|
||||
)
|
||||
found_ids = set(documents_qs.values_list("pk", flat=True))
|
||||
missing = sorted(set(document_ids) - found_ids)
|
||||
if missing:
|
||||
raise ValidationError(
|
||||
{
|
||||
"document_ids": _(
|
||||
"Documents not found: %(ids)s",
|
||||
)
|
||||
% {"ids": ", ".join(str(item) for item in missing)},
|
||||
},
|
||||
)
|
||||
|
||||
documents = list(documents_qs)
|
||||
for document in documents:
|
||||
if not has_perms_owner_aware(request.user, "view_document", document):
|
||||
raise ValidationError(
|
||||
{
|
||||
"document_ids": _(
|
||||
"Insufficient permissions to share document %(id)s.",
|
||||
)
|
||||
% {"id": document.pk},
|
||||
},
|
||||
)
|
||||
|
||||
document_map = {document.pk: document for document in documents}
|
||||
ordered_documents = [document_map[doc_id] for doc_id in document_ids]
|
||||
|
||||
bundle = serializer.save(
|
||||
owner=request.user,
|
||||
documents=ordered_documents,
|
||||
)
|
||||
bundle.remove_file()
|
||||
bundle.status = ShareLinkBundle.Status.PENDING
|
||||
bundle.last_error = ""
|
||||
bundle.size_bytes = None
|
||||
bundle.built_at = None
|
||||
bundle.file_path = ""
|
||||
bundle.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"last_error",
|
||||
"size_bytes",
|
||||
"built_at",
|
||||
"file_path",
|
||||
],
|
||||
)
|
||||
build_share_link_bundle.delay(bundle.pk)
|
||||
bundle.document_total = len(ordered_documents)
|
||||
response_serializer = self.get_serializer(bundle)
|
||||
headers = self.get_success_headers(response_serializer.data)
|
||||
return Response(
|
||||
response_serializer.data,
|
||||
status=status.HTTP_201_CREATED,
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def rebuild(self, request, pk=None):
|
||||
bundle = self.get_object()
|
||||
if bundle.status == ShareLinkBundle.Status.PROCESSING:
|
||||
return Response(
|
||||
{"detail": _("Bundle is already being processed.")},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
bundle.remove_file()
|
||||
bundle.status = ShareLinkBundle.Status.PENDING
|
||||
bundle.last_error = ""
|
||||
bundle.size_bytes = None
|
||||
bundle.built_at = None
|
||||
bundle.file_path = ""
|
||||
bundle.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"last_error",
|
||||
"size_bytes",
|
||||
"built_at",
|
||||
"file_path",
|
||||
],
|
||||
)
|
||||
build_share_link_bundle.delay(bundle.pk)
|
||||
bundle.document_total = (
|
||||
getattr(bundle, "document_total", None) or bundle.documents.count()
|
||||
)
|
||||
serializer = self.get_serializer(bundle)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class SharedLinkView(View):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request, slug):
|
||||
share_link = ShareLink.objects.filter(slug=slug).first()
|
||||
if share_link is None:
|
||||
if share_link is not None:
|
||||
if (
|
||||
share_link.expiration is not None
|
||||
and share_link.expiration < timezone.now()
|
||||
):
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_expired=1")
|
||||
return serve_file(
|
||||
doc=share_link.document,
|
||||
use_archive=share_link.file_version == "archive",
|
||||
disposition="inline",
|
||||
)
|
||||
|
||||
bundle = ShareLinkBundle.objects.filter(slug=slug).first()
|
||||
if bundle is None:
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_notfound=1")
|
||||
if share_link.expiration is not None and share_link.expiration < timezone.now():
|
||||
|
||||
if bundle.expiration is not None and bundle.expiration < timezone.now():
|
||||
return HttpResponseRedirect("/accounts/login/?sharelink_expired=1")
|
||||
return serve_file(
|
||||
doc=share_link.document,
|
||||
use_archive=share_link.file_version == "archive",
|
||||
disposition="inline",
|
||||
|
||||
if bundle.status in {
|
||||
ShareLinkBundle.Status.PENDING,
|
||||
ShareLinkBundle.Status.PROCESSING,
|
||||
}:
|
||||
return HttpResponse(
|
||||
_(
|
||||
"The share link bundle is still being prepared. Please try again later.",
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
if bundle.status == ShareLinkBundle.Status.FAILED:
|
||||
bundle.remove_file()
|
||||
bundle.status = ShareLinkBundle.Status.PENDING
|
||||
bundle.last_error = ""
|
||||
bundle.size_bytes = None
|
||||
bundle.built_at = None
|
||||
bundle.file_path = ""
|
||||
bundle.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"last_error",
|
||||
"size_bytes",
|
||||
"built_at",
|
||||
"file_path",
|
||||
],
|
||||
)
|
||||
build_share_link_bundle.delay(bundle.pk)
|
||||
return HttpResponse(
|
||||
_(
|
||||
"The share link bundle is temporarily unavailable. A rebuild has been scheduled. Please try again later.",
|
||||
),
|
||||
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
)
|
||||
|
||||
file_path = bundle.absolute_file_path
|
||||
if file_path is None or not file_path.exists():
|
||||
bundle.status = ShareLinkBundle.Status.PENDING
|
||||
bundle.last_error = ""
|
||||
bundle.size_bytes = None
|
||||
bundle.built_at = None
|
||||
bundle.file_path = ""
|
||||
bundle.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"last_error",
|
||||
"size_bytes",
|
||||
"built_at",
|
||||
"file_path",
|
||||
],
|
||||
)
|
||||
build_share_link_bundle.delay(bundle.pk)
|
||||
return HttpResponse(
|
||||
_(
|
||||
"The share link bundle is being prepared. Please try again later.",
|
||||
),
|
||||
status=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
|
||||
response = FileResponse(file_path.open("rb"), content_type="application/zip")
|
||||
short_slug = bundle.slug[:12]
|
||||
download_name = f"paperless-share-{short_slug}.zip"
|
||||
filename_normalized = (
|
||||
normalize("NFKD", download_name)
|
||||
.encode(
|
||||
"ascii",
|
||||
"ignore",
|
||||
)
|
||||
.decode("ascii")
|
||||
)
|
||||
filename_encoded = quote(download_name)
|
||||
response["Content-Disposition"] = (
|
||||
f"attachment; filename='{filename_normalized}'; "
|
||||
f"filename*=utf-8''{filename_encoded}"
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
def serve_file(*, doc: Document, use_archive: bool, disposition: str):
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: paperless-ngx\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-11-14 16:09+0000\n"
|
||||
"PO-Revision-Date: 2025-11-24 00:38\n"
|
||||
"PO-Revision-Date: 2025-11-21 23:54\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
|
||||
@@ -230,6 +230,17 @@ def _parse_beat_schedule() -> dict:
|
||||
"expires": 59.0 * 60.0,
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Cleanup expired share link bundles",
|
||||
"env_key": "PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON",
|
||||
# Default daily at 02:00
|
||||
"env_default": "0 2 * * *",
|
||||
"task": "documents.tasks.cleanup_expired_share_link_bundles",
|
||||
"options": {
|
||||
# 1 hour before default schedule sends again
|
||||
"expires": 23.0 * 60.0 * 60.0,
|
||||
},
|
||||
},
|
||||
]
|
||||
for task in tasks:
|
||||
# Either get the environment setting or use the default
|
||||
@@ -268,6 +279,7 @@ MEDIA_ROOT = __get_path("PAPERLESS_MEDIA_ROOT", BASE_DIR.parent / "media")
|
||||
ORIGINALS_DIR = MEDIA_ROOT / "documents" / "originals"
|
||||
ARCHIVE_DIR = MEDIA_ROOT / "documents" / "archive"
|
||||
THUMBNAIL_DIR = MEDIA_ROOT / "documents" / "thumbnails"
|
||||
SHARE_LINK_BUNDLE_DIR = MEDIA_ROOT / "documents" / "share_link_bundles"
|
||||
|
||||
DATA_DIR = __get_path("PAPERLESS_DATA_DIR", BASE_DIR.parent / "data")
|
||||
|
||||
|
||||
@@ -38,10 +38,19 @@ def handle_social_account_updated(sender, request, sociallogin, **kwargs):
|
||||
"""
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
social_account_groups = sociallogin.account.extra_data.get(
|
||||
extra_data = sociallogin.account.extra_data or {}
|
||||
social_account_groups = extra_data.get(
|
||||
"groups",
|
||||
[],
|
||||
) # None if not found
|
||||
) # pre-allauth 65.11.0 structure
|
||||
|
||||
if not social_account_groups:
|
||||
# allauth 65.11.0+ nests claims under `userinfo`/`id_token`
|
||||
social_account_groups = (
|
||||
extra_data.get("userinfo", {}).get("groups")
|
||||
or extra_data.get("id_token", {}).get("groups")
|
||||
or []
|
||||
)
|
||||
if settings.SOCIAL_ACCOUNT_SYNC_GROUPS and social_account_groups is not None:
|
||||
groups = Group.objects.filter(name__in=social_account_groups)
|
||||
logger.debug(
|
||||
|
||||
@@ -160,6 +160,7 @@ class TestCeleryScheduleParsing(TestCase):
|
||||
SANITY_EXPIRE_TIME = ((7.0 * 24.0) - 1.0) * 60.0 * 60.0
|
||||
EMPTY_TRASH_EXPIRE_TIME = 23.0 * 60.0 * 60.0
|
||||
RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME = 59.0 * 60.0
|
||||
CLEANUP_EXPIRED_SHARE_BUNDLES_EXPIRE_TIME = 23.0 * 60.0 * 60.0
|
||||
|
||||
def test_schedule_configuration_default(self):
|
||||
"""
|
||||
@@ -204,6 +205,13 @@ class TestCeleryScheduleParsing(TestCase):
|
||||
"schedule": crontab(minute="5", hour="*/1"),
|
||||
"options": {"expires": self.RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME},
|
||||
},
|
||||
"Cleanup expired share link bundles": {
|
||||
"task": "documents.tasks.cleanup_expired_share_link_bundles",
|
||||
"schedule": crontab(minute=0, hour=2),
|
||||
"options": {
|
||||
"expires": self.CLEANUP_EXPIRED_SHARE_BUNDLES_EXPIRE_TIME,
|
||||
},
|
||||
},
|
||||
},
|
||||
schedule,
|
||||
)
|
||||
@@ -256,6 +264,13 @@ class TestCeleryScheduleParsing(TestCase):
|
||||
"schedule": crontab(minute="5", hour="*/1"),
|
||||
"options": {"expires": self.RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME},
|
||||
},
|
||||
"Cleanup expired share link bundles": {
|
||||
"task": "documents.tasks.cleanup_expired_share_link_bundles",
|
||||
"schedule": crontab(minute=0, hour=2),
|
||||
"options": {
|
||||
"expires": self.CLEANUP_EXPIRED_SHARE_BUNDLES_EXPIRE_TIME,
|
||||
},
|
||||
},
|
||||
},
|
||||
schedule,
|
||||
)
|
||||
@@ -300,6 +315,13 @@ class TestCeleryScheduleParsing(TestCase):
|
||||
"schedule": crontab(minute="5", hour="*/1"),
|
||||
"options": {"expires": self.RUN_SCHEDULED_WORKFLOWS_EXPIRE_TIME},
|
||||
},
|
||||
"Cleanup expired share link bundles": {
|
||||
"task": "documents.tasks.cleanup_expired_share_link_bundles",
|
||||
"schedule": crontab(minute=0, hour=2),
|
||||
"options": {
|
||||
"expires": self.CLEANUP_EXPIRED_SHARE_BUNDLES_EXPIRE_TIME,
|
||||
},
|
||||
},
|
||||
},
|
||||
schedule,
|
||||
)
|
||||
@@ -322,6 +344,7 @@ class TestCeleryScheduleParsing(TestCase):
|
||||
"PAPERLESS_INDEX_TASK_CRON": "disable",
|
||||
"PAPERLESS_EMPTY_TRASH_TASK_CRON": "disable",
|
||||
"PAPERLESS_WORKFLOW_SCHEDULED_TASK_CRON": "disable",
|
||||
"PAPERLESS_SHARE_LINK_BUNDLE_CLEANUP_CRON": "disable",
|
||||
},
|
||||
):
|
||||
schedule = _parse_beat_schedule()
|
||||
|
||||
@@ -192,6 +192,68 @@ class TestSyncSocialLoginGroups(TestCase):
|
||||
)
|
||||
self.assertEqual(list(user.groups.all()), [])
|
||||
|
||||
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
||||
def test_userinfo_groups(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Enabled group syncing, and `groups` nested under `userinfo`
|
||||
WHEN:
|
||||
- The social login is updated via signal after login
|
||||
THEN:
|
||||
- The user's groups are updated using `userinfo.groups`
|
||||
"""
|
||||
group = Group.objects.create(name="group1")
|
||||
user = User.objects.create_user(username="testuser")
|
||||
sociallogin = Mock(
|
||||
user=user,
|
||||
account=Mock(
|
||||
extra_data={
|
||||
"userinfo": {
|
||||
"groups": ["group1"],
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
handle_social_account_updated(
|
||||
sender=None,
|
||||
request=HttpRequest(),
|
||||
sociallogin=sociallogin,
|
||||
)
|
||||
|
||||
self.assertEqual(list(user.groups.all()), [group])
|
||||
|
||||
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
||||
def test_id_token_groups_fallback(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Enabled group syncing, and `groups` only under `id_token`
|
||||
WHEN:
|
||||
- The social login is updated via signal after login
|
||||
THEN:
|
||||
- The user's groups are updated using `id_token.groups`
|
||||
"""
|
||||
group = Group.objects.create(name="group1")
|
||||
user = User.objects.create_user(username="testuser")
|
||||
sociallogin = Mock(
|
||||
user=user,
|
||||
account=Mock(
|
||||
extra_data={
|
||||
"id_token": {
|
||||
"groups": ["group1"],
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
handle_social_account_updated(
|
||||
sender=None,
|
||||
request=HttpRequest(),
|
||||
sociallogin=sociallogin,
|
||||
)
|
||||
|
||||
self.assertEqual(list(user.groups.all()), [group])
|
||||
|
||||
|
||||
class TestUserGroupDeletionCleanup(TestCase):
|
||||
"""
|
||||
|
||||
@@ -30,6 +30,7 @@ from documents.views import SavedViewViewSet
|
||||
from documents.views import SearchAutoCompleteView
|
||||
from documents.views import SelectionDataView
|
||||
from documents.views import SharedLinkView
|
||||
from documents.views import ShareLinkBundleViewSet
|
||||
from documents.views import ShareLinkViewSet
|
||||
from documents.views import StatisticsView
|
||||
from documents.views import StoragePathViewSet
|
||||
@@ -72,6 +73,7 @@ api_router.register(r"users", UserViewSet, basename="users")
|
||||
api_router.register(r"groups", GroupViewSet, basename="groups")
|
||||
api_router.register(r"mail_accounts", MailAccountViewSet)
|
||||
api_router.register(r"mail_rules", MailRuleViewSet)
|
||||
api_router.register(r"share_link_bundles", ShareLinkBundleViewSet)
|
||||
api_router.register(r"share_links", ShareLinkViewSet)
|
||||
api_router.register(r"workflow_triggers", WorkflowTriggerViewSet)
|
||||
api_router.register(r"workflow_actions", WorkflowActionViewSet)
|
||||
|
||||
Reference in New Issue
Block a user