Compare commits

..

1 Commits

Author SHA1 Message Date
Nathan Panchout
ed8062ea52 wip 2025-01-17 10:05:15 +01:00
98 changed files with 1598 additions and 3024 deletions

View File

@@ -1,76 +0,0 @@
name: Download translations from Crowdin
on:
workflow_dispatch:
push:
branches:
- 'release/**'
jobs:
install-front:
uses: ./.github/workflows/front-dependencies-installation.yml
with:
node_version: '20.x'
synchronize-with-crowdin:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Create empty source files
run: |
touch src/backend/locale/django.pot
mkdir -p src/frontend/packages/i18n/locales/impress/
touch src/frontend/packages/i18n/locales/impress/translations-crowdin.json
# crowdin workflow
- name: crowdin action
uses: crowdin/github-action@v2
with:
config: crowdin/config.yml
upload_sources: false
upload_translations: false
download_translations: true
create_pull_request: false
push_translations: false
push_sources: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
# Visit https://crowdin.com/settings#api-key to create this token
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
CROWDIN_BASE_PATH: "../src/"
# frontend i18n
- name: Restore the frontend cache
uses: actions/cache@v4
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
fail-on-cache-miss: true
- name: generate translations files
working-directory: src/frontend
run: yarn i18n:deploy
# Create a new PR
- name: Create a new Pull Request with new translated strings
uses: peter-evans/create-pull-request@v7
with:
commit-message: |
🌐(i18n) update translated strings
Update translated files with new translations
title: 🌐(i18n) update translated strings
body: |
## Purpose
update translated strings
## Proposal
- [x] update translated strings
branch: i18n/update-translations
labels: i18n

View File

@@ -1,67 +0,0 @@
name: Update crowdin sources
on:
workflow_dispatch:
push:
branches:
- main
jobs:
install-front:
uses: ./.github/workflows/front-dependencies-installation.yml
with:
node_version: '20.x'
synchronize-with-crowdin:
needs: install-front
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Backend i18n
- name: Install Python
uses: actions/setup-python@v3
with:
python-version: "3.12.6"
- name: Upgrade pip and setuptools
run: pip install --upgrade pip setuptools
- name: Install development dependencies
run: pip install --user .
working-directory: src/backend
- name: Install gettext
run: |
sudo apt-get update
sudo apt-get install -y gettext pandoc
- name: generate pot files
working-directory: src/backend
run: |
DJANGO_CONFIGURATION=Build python manage.py makemessages -a --keep-pot
# frontend i18n
- name: Restore the frontend cache
uses: actions/cache@v4
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
fail-on-cache-miss: true
- name: generate source translation file
working-directory: src/frontend
run: yarn i18n:extract
# crowdin workflow
- name: crowdin action
uses: crowdin/github-action@v2
with:
config: crowdin/config.yml
upload_sources: true
upload_translations: false
download_translations: false
create_pull_request: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
# Visit https://crowdin.com/settings#api-key to create this token
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
CROWDIN_BASE_PATH: "../src/"

View File

@@ -1,36 +0,0 @@
name: Install frontend installation reusable workflow
on:
workflow_call:
inputs:
node_version:
required: false
default: '20.x'
type: string
jobs:
front-dependencies-installation:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Restore the frontend cache
uses: actions/cache@v4
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
- name: Setup Node.js
if: steps.front-node_modules.outputs.cache-hit != 'true'
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
- name: Install dependencies
if: steps.front-node_modules.outputs.cache-hit != 'true'
run: cd src/frontend/ && yarn install --frozen-lockfile
- name: Cache install frontend
if: steps.front-node_modules.outputs.cache-hit != 'true'
uses: actions/cache@v4
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}

View File

@@ -9,15 +9,9 @@ on:
- "*"
jobs:
install-front:
uses: ./.github/workflows/front-dependencies-installation.yml
with:
node_version: '20.x'
test-front:
needs: install-front
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -29,10 +23,40 @@ jobs:
- name: Restore the frontend cache
uses: actions/cache@v4
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
- name: Install dependencies
if: steps.front-node_modules.outputs.cache-hit != 'true'
run: cd src/frontend/ && yarn install --frozen-lockfile
- name: Cache install frontend
if: steps.front-node_modules.outputs.cache-hit != 'true'
uses: actions/cache@v4
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
test-front:
runs-on: ubuntu-latest
needs: install-front
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Restore the frontend cache
uses: actions/cache@v4
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
fail-on-cache-miss: true
- name: Test App
run: cd src/frontend/ && yarn test
@@ -44,39 +68,29 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Restore the frontend cache
uses: actions/cache@v4
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
fail-on-cache-miss: true
- name: Check linting
run: cd src/frontend/ && yarn lint
test-e2e-chromium:
runs-on: ubuntu-latest
needs: install-front
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Restore the frontend cache
uses: actions/cache@v4
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
fail-on-cache-miss: true
- name: Set e2e env variables
run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist
@@ -127,8 +141,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install frontend dependencies
uses: ./.github/workflows/front-dependencies-installation.yml
- name: Restore the frontend cache
uses: actions/cache@v4
id: front-node_modules
with:
path: "src/frontend/**/node_modules"
key: front-node_modules-${{ hashFiles('src/frontend/**/yarn.lock') }}
- name: Set e2e env variables
run: cat env.d/development/common.e2e.dist >> env.d/development/common.dist

View File

@@ -206,11 +206,10 @@ jobs:
- name: Install development dependencies
run: pip install --user .[dev]
- name: Install gettext (required to compile messages) and MIME support
- name: Install gettext (required to compile messages)
run: |
sudo apt-get update
sudo apt-get install -y gettext pandoc shared-mime-info
sudo wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types
sudo apt-get install -y gettext pandoc
- name: Generate a MO file from strings extracted from the project
run: python manage.py compilemessages

1
.gitignore vendored
View File

@@ -30,7 +30,6 @@ MANIFEST
.next/
# Translations # Translations
*.mo
*.pot
# Environments

View File

@@ -9,30 +9,6 @@ and this project adheres to
## [Unreleased]
## Added
- github actions to managed Crowdin workflow
- 📈Integrate Posthog #540
- 🏷️(backend) add content-type to uploaded files #552
- ✨(frontend) export pdf docx front side #537
## Changed
- 💄(frontend) add abilities on doc row #581
- 💄(frontend) improve DocsGridItem responsive padding #582
## [2.0.1] - 2025-01-17
## Added
✨(frontend) add multi columns support for editor #533
## Fixed
-🐛(frontend) share modal is shown when you don't have the abilities #557
-🐛(frontend) title copy break app #564
## [2.0.0] - 2025-01-13
## Added
@@ -43,8 +19,6 @@ and this project adheres to
- 💄(frontend) add filtering to left panel #475
- ✨(frontend) new share modal ui #489
- ✨(frontend) add favorite feature #515
- 📝(documentation) Documentation about self-hosted installation #530
- ✨(helm) helm versioning #530
## Changed
@@ -56,7 +30,6 @@ and this project adheres to
- 💄(frontend) update DocHeader ui #448
- 💄(frontend) update doc versioning ui #463
- 💄(frontend) update doc summary ui #473
- 📝(doc) update readme.md to match V2 changes #558
## Fixed
@@ -380,8 +353,7 @@ and this project adheres to
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.0.1...main
[v2.0.1]: https://github.com/numerique-gouv/impress/releases/v2.0.1
[unreleased]: https://github.com/numerique-gouv/impress/compare/v2.0.0...main
[v2.0.0]: https://github.com/numerique-gouv/impress/releases/v2.0.0
[v1.10.0]: https://github.com/numerique-gouv/impress/releases/v1.10.0
[v1.9.0]: https://github.com/numerique-gouv/impress/releases/v1.9.0

View File

@@ -51,7 +51,7 @@ COPY ./src/backend /app/
WORKDIR /app
# collectstatic
RUN DJANGO_CONFIGURATION=Build \
RUN DJANGO_CONFIGURATION=Build DJANGO_JWT_PRIVATE_SIGNING_KEY=Dummy \
python manage.py collectstatic --noinput
# Replace duplicated file by a symlink to decrease the overall size of the
@@ -72,11 +72,10 @@ RUN apk add \
gettext \
gdk-pixbuf \
libffi-dev \
pandoc \
pango \
shared-mime-info
RUN wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types
# Copy entrypoint
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
@@ -93,11 +92,6 @@ COPY ./src/backend /app/
WORKDIR /app
# Generate compiled translation messages
RUN DJANGO_CONFIGURATION=Build \
python manage.py compilemessages
# We wrap commands run in this container by the following entrypoint that
# creates a user on-the-fly with the container user ID (see USER) and root group
# ID.

160
README.md
View File

@@ -1,173 +1,113 @@
<p align="center">
<a href="https://github.com/suitenumerique/docs">
<img alt="Docs" src="/docs/assets/logo-docs.png" width="300" />
</a>
</p>
# Impress
<p align="center">
Welcome to Docs! The open source document editor where your notes can become knowledge through live collaboration
</p>
Impress is a web application for real-time collaborative text editing with user and role based access rights.
Features include :
- User authentication through OIDC
- BlocNote.js text editing experience (markdown support, dynamic conversion, block structure, slash commands for block creation)
- Document export to pdf and docx from predefined templates
- Granular document permissions
- Public link sharing
- Offline mode
<p align="center">
<a href="https://matrix.to/#/#docs-official:matrix.org">
Chat on Matrix
</a> - <a href="/docs/">
Documentation
</a> - <a href="#getting-started">
Getting started
</a>
</p>
Impress is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/) and [BlocNote.js](https://www.blocknotejs.org/)
<img src="/docs/assets/docs_live_collaboration_light.gif" width="100%" align="center"/>
## Getting started
## Why use Docs ❓
Docs is a collaborative text editor designed to address common challenges in knowledge building and sharing.
### Prerequisite
### Write
* 😌 Simple collaborative editing without the formatting complexity of markdown
* 🔌 Offline? No problem, keep writing, your edits will get synced when back online
* 💅 Create clean documents with limited but beautiful formatting options and focus on content
* 🧱 Built for productivity (markdown support, many block types, slash commands, markdown support, keyboard shortcuts) (page in french sorry 😅).
* ✨ Save time thanks to our AI actions (generate, sum up, correct, translate)
Make sure you have a recent version of Docker and [Docker
Compose](https://docs.docker.com/compose/install) installed on your laptop:
### Collaborate
* 🤝 Collaborate in realtime with your team mates
* 🔒 Granular access control to keep your information secure and shared with the right people
* 📑 Professional document exports in multiple formats (.odt, .doc, .pdf) with customizable templates
* 📚 Built-in wiki functionality to transform your team's collaborative work into organized knowledge `ETA 02/2025`
### Self-host
* 🚀 Easy to install, scalable and secure alternative to Notion, Outline or Confluence
## Getting started 🔧
### Test it
Test Docs on your browser by logging in on this [environment](https://impress-preprod.beta.numerique.gouv.fr/docs/0aa856e9-da41-4d59-b73d-a61cb2c1245f/)
```
email: test.docs@yopmail.com
password: I'd<3ToTestDocs
```
### Run it locally
**Prerequisite**
Make sure you have a recent version of Docker and [Docker Compose](https://docs.docker.com/compose/install) installed on your laptop:
```shellscript
```bash
$ docker -v
Docker version 20.10.2, build 2291f61
Docker version 20.10.2, build 2291f61
$ docker compose -v
docker compose version 1.27.4, build 40524192
docker compose version 1.27.4, build 40524192
```
> ⚠️ You may need to run the following commands with sudo but this can be avoided by assigning your user to the `docker` group.
> ⚠️ You may need to run the following commands with `sudo` but this can be
> avoided by assigning your user to the `docker` group.
### Project bootstrap
**Project bootstrap**
The easiest way to start working on the project is to use GNU Make:
```shellscript
```bash
$ make bootstrap FLUSH_ARGS='--no-input'
```
This command builds the `app` container, installs dependencies, performs database migrations and compile translations. It's a good idea to use this
command each time you are pulling code from the project repository to avoid dependency-releated or migration-releated issues.
This command builds the `app` container, installs dependencies, performs
database migrations and compile translations. It's a good idea to use this
command each time you are pulling code from the project repository to avoid
dependency-releated or migration-releated issues.
Your Docker services should now be up and running 🎉
You can access to the project by going to <http://localhost:3000>.
You can access to the project by going to http://localhost:3000.
You will be prompted to log in, the default credentials are:
```shellscript
```bash
username: impress
password: impress
```
📝 Note that if you need to run them afterwards, you can use the eponym Make rule:
```shellscript
```bash
$ make run-with-frontend
```
⚠️ For the frontend developper, it is often better to run the frontend in development mode locally.
---
⚠️ For the frontend developper, it is often better to run the frontend in development mode locally.
To do so, install the frontend dependencies with the following command:
```shellscript
```bash
$ make frontend-install
```
And run the frontend locally in development mode with the following command:
```shellscript
```bash
$ make run-frontend-development
```
To start all the services, except the frontend container, you can use the following command:
```shellscript
```bash
$ make run
```
**Adding content**
---
### Adding content
You can create a basic demo site by running:
```shellscript
$ make demo
```
$ make demo
Finally, you can check all available Make rules using:
```shellscript
```bash
$ make help
```
**Django admin**
You can access the Django admin site at
### Django admin
<http://localhost:8071/admin>.
You can access the Django admin site at
[http://localhost:8071/admin](http://localhost:8071/admin).
You first need to create a superuser account:
```shellscript
```bash
$ make superuser
```
## Feedback 🙋‍♂️🙋‍♀️
We'd love to hear your thoughts and hear about your experiments, so come and say hi on [Matrix](https://matrix.to/#/#docs-official:matrix.org).
## Contributing
## Roadmap
Want to know where the project is headed? [🗺️ Checkout our roadmap](https://github.com/orgs/numerique-gouv/projects/13/views/11)
This project is intended to be community-driven, so please, do not hesitate to
get in touch if you have any question related to our implementation or design
decisions.
## Licence 📝
This work is released under the MIT License (see [LICENSE](https://github.com/suitenumerique/docs/blob/main/LICENSE)).
## License
While Docs is public driven initiative our licence choice is an invitation for private sector actors to use, sell and contribute to the project.
## Contributing 🙌
This project is intended to be community-driven, so please, do not hesitate to get in touch if you have any question related to our implementation or design decisions.
If you intend to make pull requests see CONTRIBUTING for guidelines.
Directory structure:
```markdown
docs
├── bin - executable scripts or binaries that are used for various tasks, such as setup scripts, utility scripts, or custom commands.
├── crowdin - for crowdin translations, a tool or service that helps manage translations for the project.
├── docker - Dockerfiles and related configuration files used to build Docker images for the project. These images can be used for development, testing, or production environments.
├── docs - documentation for the project, including user guides, API documentation, and other helpful resources.
├── env.d/development - environment-specific configuration files for the development environment. These files might include environment variables, configuration settings, or other setup files needed for development.
├── gitlint - configuration files for `gitlint`, a tool that enforces commit message guidelines to ensure consistency and quality in commit messages.
├── playground - experimental or temporary code, where developers can test new features or ideas without affecting the main codebase.
└── src - main source code directory, containing the core application code, libraries, and modules of the project.
```
## Credits ❤️
### Stack
Impress is built on top of [Django Rest Framework](https://www.django-rest-framework.org/), [Next.js](https://nextjs.org/), [MinIO](https://min.io/) and [BlocNote.js](https://www.blocknotejs.org/)
### States ❤️ open source
Docs is the result of a joint effort lead by the French 🇫🇷🥖 ([DINUM](https://www.numerique.gouv.fr/dinum/)) and German 🇩🇪🥨 government ([ZenDiS](https://zendis.de/)). We are always looking for new public partners feel free to reach out if you are interested in using or contributing to docs.
This work is released under the MIT License (see [LICENSE](./LICENSE)).

View File

@@ -1,7 +1,7 @@
#
# Your crowdin's credentials
#
api_token_env: CROWDIN_PERSONAL_TOKEN
api_token_env: CROWDIN_API_TOKEN
project_id_env: CROWDIN_PROJECT_ID
base_path_env: CROWDIN_BASE_PATH
@@ -15,11 +15,11 @@ preserve_hierarchy: true
# Files configuration
#
files: [
{
source : "/backend/locale/django.pot",
dest: "/backend-impress.pot",
translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po"
},
{
source : "/backend/locale/django.pot",
dest: "/backend-impress.pot",
translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po"
},
{
source: "/frontend/packages/i18n/locales/impress/translations-crowdin.json",
dest: "/frontend-impress.json",

View File

@@ -151,7 +151,7 @@ services:
image: node:18
user: "${DOCKER_USER:-1000}"
environment:
HOME: /tmp
HOME: /tmp
volumes:
- ".:/app"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -40,7 +40,6 @@ backend:
LOGIN_REDIRECT_URL: https://impress.127.0.0.1.nip.io
LOGIN_REDIRECT_URL_FAILURE: https://impress.127.0.0.1.nip.io
LOGOUT_REDIRECT_URL: https://impress.127.0.0.1.nip.io
POSTHOG_KEY: "{'id': 'posthog_key', 'host': 'https://product.impress.127.0.0.1.nip.io'}"
DB_HOST: postgresql
DB_NAME: impress
DB_USER: dinum
@@ -122,12 +121,6 @@ yProvider:
COLLABORATION_SERVER_SECRET: my-secret
Y_PROVIDER_API_KEY: my-secret
posthog:
ingress:
enabled: false
ingressAssets:
enabled: false
ingress:
enabled: true
host: impress.127.0.0.1.nip.io

View File

@@ -1,3 +1,3 @@
CROWDIN_PERSONAL_TOKEN=Your-Personal-Token
CROWDIN_API_TOKEN=Your-Api-Token
CROWDIN_PROJECT_ID=Your-Project-Id
CROWDIN_BASE_PATH=/app/src

View File

@@ -388,7 +388,6 @@ class FileUploadSerializer(serializers.Serializer):
raise serializers.ValidationError("Could not determine file extension.")
self.context["expected_extension"] = extension
self.context["content_type"] = magic_mime_type
return file
@@ -396,7 +395,6 @@ class FileUploadSerializer(serializers.Serializer):
"""Override validate to add the computed extension to validated_data."""
attrs["expected_extension"] = self.context["expected_extension"]
attrs["is_unsafe"] = self.context["is_unsafe"]
attrs["content_type"] = self.context["content_type"]
return attrs

View File

@@ -605,10 +605,7 @@ class DocumentViewSet(
key = f"{document.key_base}/{ATTACHMENTS_FOLDER:s}/{file_id!s}.{extension:s}"
# Prepare metadata for storage
extra_args = {
"Metadata": {"owner": str(request.user.id)},
"ContentType": serializer.validated_data["content_type"],
}
extra_args = {"Metadata": {"owner": str(request.user.id)}}
if serializer.validated_data["is_unsafe"]:
extra_args["Metadata"]["is_unsafe"] = "true"
@@ -939,6 +936,40 @@ class TemplateViewSet(
role=models.RoleChoices.OWNER,
)
@drf.decorators.action(
detail=True,
methods=["post"],
url_path="generate-document",
permission_classes=[permissions.AccessPermission],
)
# pylint: disable=unused-argument
def generate_document(self, request, pk=None):
"""
Generate and return a document for this template around the
body passed as argument.
2 types of body are accepted:
- HTML: body_type = "html"
- Markdown: body_type = "markdown"
2 types of documents can be generated:
- PDF: format = "pdf"
- Docx: format = "docx"
"""
serializer = serializers.DocumentGenerationSerializer(data=request.data)
if not serializer.is_valid():
return drf.response.Response(
serializer.errors, status=drf.status.HTTP_400_BAD_REQUEST
)
body = serializer.validated_data["body"]
body_type = serializer.validated_data["body_type"]
export_format = serializer.validated_data["format"]
template = self.get_object()
return template.generate_document(body, body_type, export_format)
class TemplateAccessViewSet(
ResourceAccessViewsetMixin,
@@ -1093,7 +1124,6 @@ class ConfigView(drf.views.APIView):
"ENVIRONMENT",
"FRONTEND_THEME",
"MEDIA_BASE_URL",
"POSTHOG_KEY",
"LANGUAGES",
"LANGUAGE_CODE",
"SENTRY_DSN",

View File

@@ -1,95 +0,0 @@
"""Management command updating the metadata for all the files in the MinIO bucket."""
from django.core.files.storage import default_storage
from django.core.management.base import BaseCommand
import magic
from core.models import Document
# pylint: disable=too-many-locals, broad-exception-caught
class Command(BaseCommand):
"""Update the metadata for all the files in the MinIO bucket."""
help = __doc__
def handle(self, *args, **options):
"""Execute management command."""
s3_client = default_storage.connection.meta.client
bucket_name = default_storage.bucket_name
mime_detector = magic.Magic(mime=True)
documents = Document.objects.all()
self.stdout.write(
f"[INFO] Found {documents.count()} documents. Starting ContentType fix..."
)
for doc in documents:
doc_id_str = str(doc.id)
prefix = f"{doc_id_str}/attachments/"
self.stdout.write(
f"[INFO] Processing attachments under prefix '{prefix}' ..."
)
continuation_token = None
total_updated = 0
while True:
list_kwargs = {"Bucket": bucket_name, "Prefix": prefix}
if continuation_token:
list_kwargs["ContinuationToken"] = continuation_token
response = s3_client.list_objects_v2(**list_kwargs)
# If no objects found under this prefix, break out of the loop
if "Contents" not in response:
break
for obj in response["Contents"]:
key = obj["Key"]
# Skip if it's a folder
if key.endswith("/"):
continue
try:
# Get existing metadata
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
# Read first ~1KB for MIME detection
partial_obj = s3_client.get_object(
Bucket=bucket_name, Key=key, Range="bytes=0-1023"
)
partial_data = partial_obj["Body"].read()
# Detect MIME type
magic_mime_type = mime_detector.from_buffer(partial_data)
# Update ContentType
s3_client.copy_object(
Bucket=bucket_name,
CopySource={"Bucket": bucket_name, "Key": key},
Key=key,
ContentType=magic_mime_type,
Metadata=head_resp.get("Metadata", {}),
MetadataDirective="REPLACE",
)
total_updated += 1
except Exception as exc: # noqa
self.stderr.write(
f"[ERROR] Could not update ContentType for {key}: {exc}"
)
if response.get("IsTruncated"):
continuation_token = response.get("NextContinuationToken")
else:
break
if total_updated > 0:
self.stdout.write(
f"[INFO] -> Updated {total_updated} objects for Document {doc_id_str}."
)

View File

@@ -5,8 +5,11 @@ Declare and configure the models for the impress core application
import hashlib
import smtplib
import tempfile
import textwrap
import uuid
from datetime import timedelta
from io import BytesIO
from logging import getLogger
from django.conf import settings
@@ -18,12 +21,19 @@ from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.core.mail import send_mail
from django.db import models
from django.http import FileResponse
from django.template.base import Template as DjangoTemplate
from django.template.context import Context
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils import html, timezone
from django.utils.functional import cached_property, lazy
from django.utils.translation import get_language, override
from django.utils.translation import gettext_lazy as _
import frontmatter
import markdown
import pypandoc
import weasyprint
from botocore.exceptions import ClientError
from timezone_field import TimeZoneField
@@ -784,6 +794,107 @@ class Template(BaseModel):
"retrieve": can_get,
}
def generate_pdf(self, body_html, metadata):
"""
Generate and return a pdf document wrapped around the current template
"""
document_html = weasyprint.HTML(
string=DjangoTemplate(self.code).render(
Context({"body": html.format_html(body_html), **metadata})
)
)
css = weasyprint.CSS(
string=self.css,
font_config=weasyprint.text.fonts.FontConfiguration(),
)
pdf_content = document_html.write_pdf(stylesheets=[css], zoom=1)
response = FileResponse(BytesIO(pdf_content), content_type="application/pdf")
response["Content-Disposition"] = f"attachment; filename={self.title}.pdf"
return response
def generate_word(self, body_html, metadata):
"""
Generate and return a docx document wrapped around the current template
"""
template_string = DjangoTemplate(self.code).render(
Context({"body": html.format_html(body_html), **metadata})
)
html_string = f"""
<!DOCTYPE html>
<html>
<head>
<style>
{self.css}
</style>
</head>
<body>
{template_string}
</body>
</html>
"""
reference_docx = "core/static/reference.docx"
output = BytesIO()
# Convert the HTML to a temporary docx file
with tempfile.NamedTemporaryFile(suffix=".docx", prefix="docx_") as tmp_file:
output_path = tmp_file.name
pypandoc.convert_text(
html_string,
"docx",
format="html",
outputfile=output_path,
extra_args=["--reference-doc", reference_docx],
)
# Create a BytesIO object to store the output of the temporary docx file
with open(output_path, "rb") as f:
output = BytesIO(f.read())
# Ensure the pointer is at the beginning
output.seek(0)
response = FileResponse(
output,
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = f"attachment; filename={self.title}.docx"
return response
def generate_document(self, body, body_type, export_format):
"""
Generate and return a document for this template around the
body passed as argument.
2 types of body are accepted:
- HTML: body_type = "html"
- Markdown: body_type = "markdown"
2 types of documents can be generated:
- PDF: export_format = "pdf"
- Docx: export_format = "docx"
"""
document = frontmatter.loads(body)
metadata = document.metadata
strip_body = document.content.strip()
if body_type == "html":
body_html = strip_body
else:
body_html = (
markdown.markdown(textwrap.dedent(strip_body)) if strip_body else ""
)
if export_format == "pdf":
return self.generate_pdf(body_html, metadata)
return self.generate_word(body_html, metadata)
class TemplateAccess(BaseAccess):
"""Relation model to give access to a template for a user or a team with a role."""

Binary file not shown.

View File

@@ -1,50 +0,0 @@
"""
Unit test for `update_files_content_type_metadata` command.
"""
import uuid
from django.core.files.storage import default_storage
from django.core.management import call_command
import pytest
from core import factories
@pytest.mark.django_db
def test_update_files_content_type_metadata():
"""
Test that the command `update_files_content_type_metadata`
fixes the ContentType of attachment in the storage.
"""
s3_client = default_storage.connection.meta.client
bucket_name = default_storage.bucket_name
# Create files with a wrong ContentType
keys = []
for _ in range(10):
doc_id = uuid.uuid4()
factories.DocumentFactory(id=doc_id)
key = f"{doc_id}/attachments/testfile.png"
keys.append(key)
fake_png = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR..."
s3_client.put_object(
Bucket=bucket_name,
Key=key,
Body=fake_png,
ContentType="text/plain",
Metadata={"owner": "None"},
)
# Call the command that fixes the ContentType
call_command("update_files_content_type_metadata")
for key in keys:
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
assert (
head_resp["ContentType"] == "image/png"
), f"ContentType not fixed, got {head_resp['ContentType']!r}"
# Check that original metadata was preserved
assert head_resp["Metadata"].get("owner") == "None"

View File

@@ -64,22 +64,12 @@ def test_api_documents_attachment_upload_anonymous_success():
assert response.status_code == 201
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.png")
file_path = response.json()["file"]
match = pattern.search(file_path)
match = pattern.search(response.json()["file"])
file_id = match.group(1)
# Validate that file_id is a valid UUID
uuid.UUID(file_id)
# Now, check the metadata of the uploaded file
key = file_path.replace("/media", "")
file_head = default_storage.connection.meta.client.head_object(
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": "None"}
assert file_head["ContentType"] == "image/png"
@pytest.mark.parametrize(
"reach, role",
@@ -216,7 +206,6 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": str(user.id)}
assert file_head["ContentType"] == "image/png"
def test_api_documents_attachment_upload_invalid(client):
@@ -258,18 +247,16 @@ def test_api_documents_attachment_upload_size_limit_exceeded(settings):
@pytest.mark.parametrize(
"name,content,extension,content_type",
"name,content,extension",
[
("test.exe", b"text", "exe", "text/plain"),
("test", b"text", "txt", "text/plain"),
("test.aaaaaa", b"test", "txt", "text/plain"),
("test.txt", PIXEL, "txt", "image/png"),
("test.py", b"#!/usr/bin/python", "py", "text/plain"),
("test.exe", b"text", "exe"),
("test", b"text", "txt"),
("test.aaaaaa", b"test", "txt"),
("test.txt", PIXEL, "txt"),
("test.py", b"#!/usr/bin/python", "py"),
],
)
def test_api_documents_attachment_upload_fix_extension(
name, content, extension, content_type
):
def test_api_documents_attachment_upload_fix_extension(name, content, extension):
"""
A file with no extension or a wrong extension is accepted and the extension
is corrected in storage.
@@ -300,7 +287,6 @@ def test_api_documents_attachment_upload_fix_extension(
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
assert file_head["ContentType"] == content_type
def test_api_documents_attachment_upload_empty_file():
@@ -349,4 +335,3 @@ def test_api_documents_attachment_upload_unsafe():
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
assert file_head["ContentType"] == "application/octet-stream"

View File

@@ -0,0 +1,208 @@
"""
Test users API endpoints in the impress core app.
"""
import pytest
from rest_framework.test import APIClient
from core import factories
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db
def test_api_templates_generate_document_anonymous_public():
"""Anonymous users can generate pdf document with public templates."""
template = factories.TemplateFactory(is_public=True)
data = {
"body": "# Test markdown body",
}
response = APIClient().post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_anonymous_not_public():
"""
Anonymous users should not be allowed to generate pdf document with templates
that are not marked as public.
"""
template = factories.TemplateFactory(is_public=False)
data = {
"body": "# Test markdown body",
}
response = APIClient().post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_templates_generate_document_authenticated_public():
"""Authenticated users can generate pdf document with public templates."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
template = factories.TemplateFactory(is_public=True)
data = {"body": "# Test markdown body"}
response = client.post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_authenticated_not_public():
"""
Authenticated users should not be allowed to generate pdf document with templates
that are not marked as public.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
template = factories.TemplateFactory(is_public=False)
data = {"body": "# Test markdown body"}
response = client.post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
@pytest.mark.parametrize("via", VIA)
def test_api_templates_generate_document_related(via, mock_user_teams):
"""Users related to a template can generate pdf document."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
access = None
if via == USER:
access = factories.UserTemplateAccessFactory(user=user)
elif via == TEAM:
mock_user_teams.return_value = ["lasuite", "unknown"]
access = factories.TeamTemplateAccessFactory(team="lasuite")
data = {"body": "# Test markdown body"}
response = client.post(
f"/api/v1.0/templates/{access.template_id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_type_html():
"""Generate pdf document with the body type html."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
template = factories.TemplateFactory(is_public=True)
data = {"body": "<p>Test body</p>", "body_type": "html"}
response = client.post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_type_markdown():
"""Generate pdf document with the body type markdown."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
template = factories.TemplateFactory(is_public=True)
data = {"body": "# Test markdown body", "body_type": "markdown"}
response = client.post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
def test_api_templates_generate_document_type_unknown():
"""Generate pdf document with the body type unknown."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
template = factories.TemplateFactory(is_public=True)
data = {"body": "# Test markdown body", "body_type": "unknown"}
response = client.post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 400
assert response.json() == {
"body_type": [
'"unknown" is not a valid choice.',
]
}
def test_api_templates_generate_document_export_docx():
"""Generate pdf document with the body type html."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
template = factories.TemplateFactory(is_public=True)
data = {"body": "<p>Test body</p>", "body_type": "html", "format": "docx"}
response = client.post(
f"/api/v1.0/templates/{template.id!s}/generate-document/",
data,
format="json",
)
assert response.status_code == 200
assert (
response.headers["content-type"]
== "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)

View File

@@ -20,7 +20,6 @@ pytestmark = pytest.mark.django_db
CRISP_WEBSITE_ID="123",
FRONTEND_THEME="test-theme",
MEDIA_BASE_URL="http://testserver/",
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
SENTRY_DSN="https://sentry.test/123",
)
@pytest.mark.parametrize("is_authenticated", [False, True])
@@ -42,6 +41,5 @@ def test_api_config(is_authenticated):
"LANGUAGES": [["en-us", "English"], ["fr-fr", "French"], ["de-de", "German"]],
"LANGUAGE_CODE": "en-us",
"MEDIA_BASE_URL": "http://testserver/",
"POSTHOG_KEY": {"id": "132456", "host": "https://eu.i.posthog-test.com"},
"SENTRY_DSN": "https://sentry.test/123",
}

View File

@@ -2,6 +2,10 @@
Unit tests for the Template model
"""
import os
import time
from unittest import mock
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ValidationError
@@ -185,3 +189,31 @@ def test_models_templates_get_abilities_preset_role(django_assert_num_queries):
"partial_update": False,
"generate_document": True,
}
def test_models_templates__generate_word():
"""Generate word document and assert no tmp files are left in /tmp folder."""
template = factories.TemplateFactory()
response = template.generate_word("<p>Test body</p>", {})
assert response.status_code == 200
assert len([f for f in os.listdir("/tmp") if f.startswith("docx_")]) == 0
@mock.patch(
"pypandoc.convert_text",
side_effect=RuntimeError("Conversion failed"),
)
def test_models_templates__generate_word__raise_error(_mock_pypandoc):
"""
Generate word document and assert no tmp files are left in /tmp folder
even when the conversion fails.
"""
template = factories.TemplateFactory()
try:
template.generate_word("<p>Test body</p>", {})
except RuntimeError as e:
assert str(e) == "Conversion failed"
time.sleep(0.5)
assert len([f for f in os.listdir("/tmp") if f.startswith("docx_")]) == 0

View File

@@ -1,2 +1,10 @@
<img width="200" src="https://impress-staging.beta.numerique.gouv.fr/assets/logo-gouv.png" />
<br/>
<page size="A4">
<div class="header">
<img width="200"
src="https://impress-staging.beta.numerique.gouv.fr/assets/logo-gouv.png"
/>
</div>
<div class="content">
<div class="body">{{ body }}</div>
</div>
</page>

View File

@@ -0,0 +1,20 @@
body {
background: white;
font-family: arial;
}
.header img {
width: 5cm;
margin-left: -0.4cm;
}
.body{
margin-top: 1.5rem;
}
img {
max-width: 100%;
}
[custom-style="center"] {
text-align: center;
}
[custom-style="right"] {
text-align: right;
}

View File

@@ -390,11 +390,6 @@ class Base(Configuration):
None, environ_name="FRONTEND_THEME", environ_prefix=None
)
# Posthog
POSTHOG_KEY = values.DictValue(
None, environ_name="POSTHOG_KEY", environ_prefix=None
)
# Crisp
CRISP_WEBSITE_ID = values.Value(
None, environ_name="CRISP_WEBSITE_ID", environ_prefix=None

Binary file not shown.

View File

@@ -1,9 +1,9 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Project-Id-Version: lasuite-people\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
"PO-Revision-Date: 2025-01-27 09:27\n"
"POT-Creation-Date: 2024-12-17 15:50+0000\n"
"PO-Revision-Date: 2025-01-14 15:14\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -11,342 +11,384 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Project: lasuite-people\n"
"X-Crowdin-Project-ID: 637934\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
"X-Crowdin-File-ID: 8\n"
#: build/lib/core/admin.py:33 core/admin.py:33
#: core/admin.py:33
msgid "Personal info"
msgstr "Persönliche Daten"
#: build/lib/core/admin.py:46 core/admin.py:46
#: core/admin.py:46
msgid "Permissions"
msgstr "Berechtigungen"
#: build/lib/core/admin.py:58 core/admin.py:58
#: core/admin.py:58
msgid "Important dates"
msgstr "Wichtige Daten"
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: core/api/filters.py:16
msgid "Creator is me"
msgstr "Ersteller bin ich"
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
#: core/api/filters.py:19
msgid "Favorite"
msgstr "Favorit"
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
#: core/api/filters.py:22
msgid "Title"
msgstr "Titel"
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
#: core/api/serializers.py:307
msgid "A new document was created on your behalf!"
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
#: core/api/serializers.py:311
msgid "You have been granted ownership of a new document:"
msgstr "Sie sind Besitzer eines neuen Dokuments:"
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
#: core/api/serializers.py:414
msgid "Body"
msgstr "Inhalt"
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
#: core/api/serializers.py:417
msgid "Body type"
msgstr "Typ"
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
#: core/api/serializers.py:423
msgid "Format"
msgstr ""
msgstr "Format"
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
#: core/authentication/backends.py:57
msgid "Invalid response format or token verification failed"
msgstr "Ungültiges Antwortformat oder Token-Verifizierung fehlgeschlagen"
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
#: core/authentication/backends.py:81
msgid "User info contained no recognizable user identification"
msgstr "Benutzerinfo enthielt keine erkennbare Benutzeridentifikation"
#: core/authentication/backends.py:88
msgid "User account is disabled"
msgstr "Benutzerkonto ist deaktiviert"
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
#: core/models.py:70
#: core/models.py:62 core/models.py:69
msgid "Reader"
msgstr "Lesen"
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
#: core/models.py:71
#: core/models.py:63 core/models.py:70
msgid "Editor"
msgstr "Bearbeiten"
#: build/lib/core/models.py:72 core/models.py:72
#: core/models.py:71
msgid "Administrator"
msgstr ""
msgstr "Administrator"
#: build/lib/core/models.py:73 core/models.py:73
#: core/models.py:72
msgid "Owner"
msgstr "Besitzer"
#: build/lib/core/models.py:84 core/models.py:84
#: core/models.py:83
msgid "Restricted"
msgstr "Beschränkt"
#: build/lib/core/models.py:88 core/models.py:88
#: core/models.py:87
msgid "Authenticated"
msgstr "Authentifiziert"
#: build/lib/core/models.py:90 core/models.py:90
#: core/models.py:89
msgid "Public"
msgstr "Öffentlich"
#: build/lib/core/models.py:112 core/models.py:112
#: core/models.py:101
msgid "id"
msgstr ""
#: build/lib/core/models.py:113 core/models.py:113
#: core/models.py:102
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:119 core/models.py:119
#: core/models.py:108
msgid "created on"
msgstr "Erstellt"
#: build/lib/core/models.py:120 core/models.py:120
#: core/models.py:109
msgid "date and time at which a record was created"
msgstr "Datum und Uhrzeit, an dem ein Datensatz erstellt wurde"
#: build/lib/core/models.py:125 core/models.py:125
#: core/models.py:114
msgid "updated on"
msgstr "Aktualisiert"
#: build/lib/core/models.py:126 core/models.py:126
#: core/models.py:115
msgid "date and time at which a record was last updated"
msgstr "Datum und Uhrzeit, an dem zuletzt aktualisiert wurde"
#: build/lib/core/models.py:162 core/models.py:162
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:175 core/models.py:175
#: core/models.py:135
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr "Geben Sie eine gültige Unterseite ein. Dieser Wert darf nur Buchstaben, Zahlen und die @/./+/-/_/: Zeichen enthalten."
#: build/lib/core/models.py:181 core/models.py:181
#: core/models.py:141
msgid "sub"
msgstr "unter"
#: build/lib/core/models.py:183 core/models.py:183
#: core/models.py:143
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr "Erforderlich. 255 Zeichen oder weniger. Buchstaben, Zahlen und die Zeichen @/./+/-/_/:"
#: build/lib/core/models.py:192 core/models.py:192
#: core/models.py:152
msgid "full name"
msgstr "Name"
#: build/lib/core/models.py:193 core/models.py:193
#: core/models.py:153
msgid "short name"
msgstr "Kurzbezeichnung"
#: build/lib/core/models.py:195 core/models.py:195
#: core/models.py:155
msgid "identity email address"
msgstr "Identitäts-E-Mail-Adresse"
#: build/lib/core/models.py:200 core/models.py:200
#: core/models.py:160
msgid "admin email address"
msgstr "Admin E-Mail-Adresse"
#: build/lib/core/models.py:207 core/models.py:207
#: core/models.py:167
msgid "language"
msgstr "Sprache"
#: build/lib/core/models.py:208 core/models.py:208
#: core/models.py:168
msgid "The language in which the user wants to see the interface."
msgstr "Die Sprache, in der der Benutzer die Benutzeroberfläche sehen möchte."
#: build/lib/core/models.py:214 core/models.py:214
#: core/models.py:174
msgid "The timezone in which the user wants to see times."
msgstr "Die Zeitzone, in der der Nutzer Zeiten sehen möchte."
#: build/lib/core/models.py:217 core/models.py:217
#: core/models.py:177
msgid "device"
msgstr "Gerät"
#: build/lib/core/models.py:219 core/models.py:219
#: core/models.py:179
msgid "Whether the user is a device or a real user."
msgstr "Ob der Benutzer ein Gerät oder ein echter Benutzer ist."
#: build/lib/core/models.py:222 core/models.py:222
#: core/models.py:182
msgid "staff status"
msgstr "Status des Teammitgliedes"
#: build/lib/core/models.py:224 core/models.py:224
#: core/models.py:184
msgid "Whether the user can log into this admin site."
msgstr "Gibt an, ob der Benutzer sich in diese Admin-Seite einloggen kann."
#: build/lib/core/models.py:227 core/models.py:227
#: core/models.py:187
msgid "active"
msgstr "aktiviert"
#: build/lib/core/models.py:230 core/models.py:230
#: core/models.py:190
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr "Ob dieser Benutzer als aktiviert behandelt werden soll. Deaktivieren Sie diese Option, anstatt Konten zu löschen."
#: build/lib/core/models.py:242 core/models.py:242
#: core/models.py:202
msgid "user"
msgstr "Benutzer"
#: build/lib/core/models.py:243 core/models.py:243
#: core/models.py:203
msgid "users"
msgstr "Benutzer"
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
#: core/models.py:758
#: core/models.py:342 core/models.py:718
msgid "title"
msgstr "Titel"
#: build/lib/core/models.py:404 core/models.py:404
#: core/models.py:364
msgid "Document"
msgstr "Dokument"
#: build/lib/core/models.py:405 core/models.py:405
#: core/models.py:365
msgid "Documents"
msgstr "Dokumente"
#: build/lib/core/models.py:408 core/models.py:408
#: core/models.py:368
msgid "Untitled Document"
msgstr "Unbenanntes Dokument"
#: build/lib/core/models.py:633 core/models.py:633
#: core/models.py:593
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
#: build/lib/core/models.py:637 core/models.py:637
#: core/models.py:597
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} hat Sie mit der Rolle \"{role}\" zu folgendem Dokument eingeladen:"
#: build/lib/core/models.py:640 core/models.py:640
#: core/models.py:600
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} hat ein Dokument mit Ihnen geteilt: {title}"
#: build/lib/core/models.py:663 core/models.py:663
#: core/models.py:623
msgid "Document/user link trace"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:664 core/models.py:664
#: core/models.py:624
msgid "Document/user link traces"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:670 core/models.py:670
#: core/models.py:630
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:693 core/models.py:693
#: core/models.py:653
msgid "Document favorite"
msgstr "Dokumentenfavorit"
#: build/lib/core/models.py:694 core/models.py:694
#: core/models.py:654
msgid "Document favorites"
msgstr "Dokumentfavoriten"
#: build/lib/core/models.py:700 core/models.py:700
#: core/models.py:660
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Dieses Dokument ist bereits durch den gleichen Benutzer favorisiert worden."
#: build/lib/core/models.py:722 core/models.py:722
#: core/models.py:682
msgid "Document/user relation"
msgstr "Dokument/Benutzerbeziehung"
#: build/lib/core/models.py:723 core/models.py:723
#: core/models.py:683
msgid "Document/user relations"
msgstr "Dokument/Benutzerbeziehungen"
#: build/lib/core/models.py:729 core/models.py:729
#: core/models.py:689
msgid "This user is already in this document."
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:735 core/models.py:735
#: core/models.py:695
msgid "This team is already in this document."
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
#: core/models.py:930
#: core/models.py:701 core/models.py:890
msgid "Either user or team must be set, not both."
msgstr "Benutzer oder Team müssen gesetzt werden, nicht beides."
#: build/lib/core/models.py:759 core/models.py:759
#: core/models.py:719
msgid "description"
msgstr "Beschreibung"
#: build/lib/core/models.py:760 core/models.py:760
#: core/models.py:720
msgid "code"
msgstr "Code"
#: build/lib/core/models.py:761 core/models.py:761
#: core/models.py:721
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:763 core/models.py:763
#: core/models.py:723
msgid "public"
msgstr "öffentlich"
#: build/lib/core/models.py:765 core/models.py:765
#: core/models.py:725
msgid "Whether this template is public for anyone to use."
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
#: build/lib/core/models.py:771 core/models.py:771
#: core/models.py:731
msgid "Template"
msgstr "Vorlage"
#: build/lib/core/models.py:772 core/models.py:772
#: core/models.py:732
msgid "Templates"
msgstr "Vorlagen"
#: build/lib/core/models.py:911 core/models.py:911
#: core/models.py:871
msgid "Template/user relation"
msgstr "Vorlage/Benutzer-Beziehung"
#: build/lib/core/models.py:912 core/models.py:912
#: core/models.py:872
msgid "Template/user relations"
msgstr "Vorlage/Benutzerbeziehungen"
#: build/lib/core/models.py:918 core/models.py:918
#: core/models.py:878
msgid "This user is already in this template."
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
#: build/lib/core/models.py:924 core/models.py:924
#: core/models.py:884
msgid "This team is already in this template."
msgstr "Dieses Team ist bereits in diesem Template."
#: build/lib/core/models.py:947 core/models.py:947
#: core/models.py:907
msgid "email address"
msgstr "E-Mail-Adresse"
#: build/lib/core/models.py:966 core/models.py:966
#: core/models.py:926
msgid "Document invitation"
msgstr "Einladung zum Dokument"
#: build/lib/core/models.py:967 core/models.py:967
#: core/models.py:927
msgid "Document invitations"
msgstr "Dokumenteinladungen"
#: build/lib/core/models.py:987 core/models.py:987
#: core/models.py:944
msgid "This email is already associated to a registered user."
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
#: build/lib/impress/settings.py:236 impress/settings.py:236
#: core/templates/mail/html/hello.html:159 core/templates/mail/text/hello.txt:3
msgid "Company logo"
msgstr "Unternehmens-Logo"
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
#, python-format
msgid "Hello %(name)s"
msgstr "Guten Tag %(name)s!"
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
msgid "Hello"
msgstr "Hallo"
#: core/templates/mail/html/hello.html:189 core/templates/mail/text/hello.txt:6
msgid "Thank you very much for your visit!"
msgstr "Vielen Dank für Ihren Besuch!"
#: core/templates/mail/html/hello.html:221
#, python-format
msgid "This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
msgstr "Diese E-Mail wurde an %(email)s von <a href=\"%(href)s\">%(name)s</a> gesendet"
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr "Logo-E-Mail"
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Öffnen"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr " Docs, Ihr neues unentbehrliches Werkzeug für die Organisation, den Austausch und die Zusammenarbeit in Ihren Dokumenten als Team. "
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr " Erstellt von %(brandname)s "
#: core/templates/mail/text/hello.txt:8
#, python-format
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
msgstr "Diese E-Mail wurde an %(email)s von %(name)s [%(href)s ] gesendet"
#: impress/settings.py:236
msgid "English"
msgstr "Englisch"
#: build/lib/impress/settings.py:237 impress/settings.py:237
#: impress/settings.py:237
msgid "French"
msgstr "Französisch"
#: build/lib/impress/settings.py:238 impress/settings.py:238
#: impress/settings.py:238
msgid "German"
msgstr "Deutsch"

Binary file not shown.

View File

@@ -1,9 +1,9 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Project-Id-Version: lasuite-people\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
"PO-Revision-Date: 2025-01-27 09:27\n"
"POT-Creation-Date: 2024-12-17 15:50+0000\n"
"PO-Revision-Date: 2024-12-17 15:53\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -11,342 +11,384 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Project: lasuite-people\n"
"X-Crowdin-Project-ID: 637934\n"
"X-Crowdin-Language: en\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
"X-Crowdin-File-ID: 8\n"
#: build/lib/core/admin.py:33 core/admin.py:33
#: core/admin.py:33
msgid "Personal info"
msgstr ""
#: build/lib/core/admin.py:46 core/admin.py:46
#: core/admin.py:46
msgid "Permissions"
msgstr ""
#: build/lib/core/admin.py:58 core/admin.py:58
#: core/admin.py:58
msgid "Important dates"
msgstr ""
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: core/api/filters.py:16
msgid "Creator is me"
msgstr ""
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
#: core/api/filters.py:19
msgid "Favorite"
msgstr ""
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
#: core/api/filters.py:22
msgid "Title"
msgstr ""
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
#: core/api/serializers.py:307
msgid "A new document was created on your behalf!"
msgstr ""
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
#: core/api/serializers.py:311
msgid "You have been granted ownership of a new document:"
msgstr ""
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
#: core/api/serializers.py:414
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
#: core/api/serializers.py:417
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
#: core/api/serializers.py:423
msgid "Format"
msgstr ""
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
#: core/authentication/backends.py:57
msgid "Invalid response format or token verification failed"
msgstr ""
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
#: core/authentication/backends.py:81
msgid "User info contained no recognizable user identification"
msgstr ""
#: core/authentication/backends.py:88
msgid "User account is disabled"
msgstr ""
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
#: core/models.py:70
#: core/models.py:62 core/models.py:69
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
#: core/models.py:71
#: core/models.py:63 core/models.py:70
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:72 core/models.py:72
#: core/models.py:71
msgid "Administrator"
msgstr ""
#: build/lib/core/models.py:73 core/models.py:73
#: core/models.py:72
msgid "Owner"
msgstr ""
#: build/lib/core/models.py:84 core/models.py:84
#: core/models.py:83
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:88 core/models.py:88
#: core/models.py:87
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:90 core/models.py:90
#: core/models.py:89
msgid "Public"
msgstr ""
#: build/lib/core/models.py:112 core/models.py:112
#: core/models.py:101
msgid "id"
msgstr ""
#: build/lib/core/models.py:113 core/models.py:113
#: core/models.py:102
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:119 core/models.py:119
#: core/models.py:108
msgid "created on"
msgstr ""
#: build/lib/core/models.py:120 core/models.py:120
#: core/models.py:109
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:125 core/models.py:125
#: core/models.py:114
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:126 core/models.py:126
#: core/models.py:115
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:175 core/models.py:175
#: core/models.py:135
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:181 core/models.py:181
#: core/models.py:141
msgid "sub"
msgstr ""
#: build/lib/core/models.py:183 core/models.py:183
#: core/models.py:143
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:192 core/models.py:192
#: core/models.py:152
msgid "full name"
msgstr ""
#: build/lib/core/models.py:193 core/models.py:193
#: core/models.py:153
msgid "short name"
msgstr ""
#: build/lib/core/models.py:195 core/models.py:195
#: core/models.py:155
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:200 core/models.py:200
#: core/models.py:160
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:207 core/models.py:207
#: core/models.py:167
msgid "language"
msgstr ""
#: build/lib/core/models.py:208 core/models.py:208
#: core/models.py:168
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:214 core/models.py:214
#: core/models.py:174
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
#: core/models.py:177
msgid "device"
msgstr ""
#: build/lib/core/models.py:219 core/models.py:219
#: core/models.py:179
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:222 core/models.py:222
#: core/models.py:182
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:224 core/models.py:224
#: core/models.py:184
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:227 core/models.py:227
#: core/models.py:187
msgid "active"
msgstr ""
#: build/lib/core/models.py:230 core/models.py:230
#: core/models.py:190
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
#: core/models.py:202
msgid "user"
msgstr ""
#: build/lib/core/models.py:243 core/models.py:243
#: core/models.py:203
msgid "users"
msgstr ""
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
#: core/models.py:758
#: core/models.py:342 core/models.py:718
msgid "title"
msgstr ""
#: build/lib/core/models.py:404 core/models.py:404
#: core/models.py:364
msgid "Document"
msgstr ""
#: build/lib/core/models.py:405 core/models.py:405
#: core/models.py:365
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:408 core/models.py:408
#: core/models.py:368
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:633 core/models.py:633
#: core/models.py:593
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:637 core/models.py:637
#: core/models.py:597
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:640 core/models.py:640
#: core/models.py:600
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:663 core/models.py:663
#: core/models.py:623
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:664 core/models.py:664
#: core/models.py:624
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:670 core/models.py:670
#: core/models.py:630
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:693 core/models.py:693
#: core/models.py:653
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:694 core/models.py:694
#: core/models.py:654
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:700 core/models.py:700
#: core/models.py:660
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:722 core/models.py:722
#: core/models.py:682
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:723 core/models.py:723
#: core/models.py:683
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:729 core/models.py:729
#: core/models.py:689
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:735 core/models.py:735
#: core/models.py:695
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
#: core/models.py:930
#: core/models.py:701 core/models.py:890
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:759 core/models.py:759
#: core/models.py:719
msgid "description"
msgstr ""
#: build/lib/core/models.py:760 core/models.py:760
#: core/models.py:720
msgid "code"
msgstr ""
#: build/lib/core/models.py:761 core/models.py:761
#: core/models.py:721
msgid "css"
msgstr ""
#: build/lib/core/models.py:763 core/models.py:763
#: core/models.py:723
msgid "public"
msgstr ""
#: build/lib/core/models.py:765 core/models.py:765
#: core/models.py:725
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:771 core/models.py:771
#: core/models.py:731
msgid "Template"
msgstr ""
#: build/lib/core/models.py:772 core/models.py:772
#: core/models.py:732
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: core/models.py:871
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:912 core/models.py:912
#: core/models.py:872
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:918 core/models.py:918
#: core/models.py:878
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:924 core/models.py:924
#: core/models.py:884
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:947 core/models.py:947
#: core/models.py:907
msgid "email address"
msgstr ""
#: build/lib/core/models.py:966 core/models.py:966
#: core/models.py:926
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:967 core/models.py:967
#: core/models.py:927
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:987 core/models.py:987
#: core/models.py:944
msgid "This email is already associated to a registered user."
msgstr ""
#: build/lib/impress/settings.py:236 impress/settings.py:236
#: core/templates/mail/html/hello.html:159 core/templates/mail/text/hello.txt:3
msgid "Company logo"
msgstr ""
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
#, python-format
msgid "Hello %(name)s"
msgstr ""
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
msgid "Hello"
msgstr ""
#: core/templates/mail/html/hello.html:189 core/templates/mail/text/hello.txt:6
msgid "Thank you very much for your visit!"
msgstr ""
#: core/templates/mail/html/hello.html:221
#, python-format
msgid "This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
msgstr ""
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr ""
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr ""
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr ""
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr ""
#: core/templates/mail/text/hello.txt:8
#, python-format
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
msgstr ""
#: impress/settings.py:236
msgid "English"
msgstr ""
#: build/lib/impress/settings.py:237 impress/settings.py:237
#: impress/settings.py:237
msgid "French"
msgstr ""
#: build/lib/impress/settings.py:238 impress/settings.py:238
#: impress/settings.py:238
msgid "German"
msgstr ""

Binary file not shown.

View File

@@ -1,9 +1,9 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Project-Id-Version: lasuite-people\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
"PO-Revision-Date: 2025-01-27 09:27\n"
"POT-Creation-Date: 2024-12-17 15:50+0000\n"
"PO-Revision-Date: 2024-12-17 15:53\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -11,342 +11,384 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Project: lasuite-people\n"
"X-Crowdin-Project-ID: 637934\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
"X-Crowdin-File-ID: 8\n"
#: build/lib/core/admin.py:33 core/admin.py:33
#: core/admin.py:33
msgid "Personal info"
msgstr "Infos Personnelles"
#: build/lib/core/admin.py:46 core/admin.py:46
#: core/admin.py:46
msgid "Permissions"
msgstr ""
msgstr "Permissions"
#: build/lib/core/admin.py:58 core/admin.py:58
#: core/admin.py:58
msgid "Important dates"
msgstr "Dates importantes"
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
#: core/api/filters.py:16
msgid "Creator is me"
msgstr ""
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
#: core/api/filters.py:19
msgid "Favorite"
msgstr ""
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
#: core/api/filters.py:22
msgid "Title"
msgstr ""
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
#: core/api/serializers.py:307
msgid "A new document was created on your behalf!"
msgstr "Un nouveau document a été créé pour vous !"
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
#: core/api/serializers.py:311
msgid "You have been granted ownership of a new document:"
msgstr "Vous avez été déclaré propriétaire d'un nouveau document :"
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
#: core/api/serializers.py:414
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
#: core/api/serializers.py:417
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
#: core/api/serializers.py:423
msgid "Format"
msgstr ""
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
#: core/authentication/backends.py:57
msgid "Invalid response format or token verification failed"
msgstr ""
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
#: core/authentication/backends.py:81
msgid "User info contained no recognizable user identification"
msgstr ""
#: core/authentication/backends.py:88
msgid "User account is disabled"
msgstr ""
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
#: core/models.py:70
#: core/models.py:62 core/models.py:69
msgid "Reader"
msgstr "Lecteur"
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
#: core/models.py:71
#: core/models.py:63 core/models.py:70
msgid "Editor"
msgstr "Éditeur"
#: build/lib/core/models.py:72 core/models.py:72
#: core/models.py:71
msgid "Administrator"
msgstr "Administrateur"
#: build/lib/core/models.py:73 core/models.py:73
#: core/models.py:72
msgid "Owner"
msgstr "Propriétaire"
#: build/lib/core/models.py:84 core/models.py:84
#: core/models.py:83
msgid "Restricted"
msgstr "Restreint"
#: build/lib/core/models.py:88 core/models.py:88
#: core/models.py:87
msgid "Authenticated"
msgstr "Authentifié"
#: build/lib/core/models.py:90 core/models.py:90
#: core/models.py:89
msgid "Public"
msgstr ""
msgstr "Public"
#: build/lib/core/models.py:112 core/models.py:112
#: core/models.py:101
msgid "id"
msgstr ""
#: build/lib/core/models.py:113 core/models.py:113
#: core/models.py:102
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:119 core/models.py:119
#: core/models.py:108
msgid "created on"
msgstr ""
#: build/lib/core/models.py:120 core/models.py:120
#: core/models.py:109
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:125 core/models.py:125
#: core/models.py:114
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:126 core/models.py:126
#: core/models.py:115
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:175 core/models.py:175
#: core/models.py:135
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:181 core/models.py:181
#: core/models.py:141
msgid "sub"
msgstr ""
#: build/lib/core/models.py:183 core/models.py:183
#: core/models.py:143
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:192 core/models.py:192
#: core/models.py:152
msgid "full name"
msgstr ""
#: build/lib/core/models.py:193 core/models.py:193
#: core/models.py:153
msgid "short name"
msgstr ""
#: build/lib/core/models.py:195 core/models.py:195
#: core/models.py:155
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:200 core/models.py:200
#: core/models.py:160
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:207 core/models.py:207
#: core/models.py:167
msgid "language"
msgstr ""
#: build/lib/core/models.py:208 core/models.py:208
#: core/models.py:168
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:214 core/models.py:214
#: core/models.py:174
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
#: core/models.py:177
msgid "device"
msgstr ""
#: build/lib/core/models.py:219 core/models.py:219
#: core/models.py:179
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:222 core/models.py:222
#: core/models.py:182
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:224 core/models.py:224
#: core/models.py:184
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:227 core/models.py:227
#: core/models.py:187
msgid "active"
msgstr ""
#: build/lib/core/models.py:230 core/models.py:230
#: core/models.py:190
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
#: core/models.py:202
msgid "user"
msgstr ""
#: build/lib/core/models.py:243 core/models.py:243
#: core/models.py:203
msgid "users"
msgstr ""
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
#: core/models.py:758
#: core/models.py:342 core/models.py:718
msgid "title"
msgstr ""
#: build/lib/core/models.py:404 core/models.py:404
#: core/models.py:364
msgid "Document"
msgstr ""
#: build/lib/core/models.py:405 core/models.py:405
#: core/models.py:365
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:408 core/models.py:408
#: core/models.py:368
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:633 core/models.py:633
#: core/models.py:593
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} a partagé un document avec vous!"
#: build/lib/core/models.py:637 core/models.py:637
#: core/models.py:597
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} vous a invité avec le rôle \"{role}\" sur le document suivant:"
#: build/lib/core/models.py:640 core/models.py:640
#: core/models.py:600
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} a partagé un document avec vous: {title}"
#: build/lib/core/models.py:663 core/models.py:663
#: core/models.py:623
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:664 core/models.py:664
#: core/models.py:624
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:670 core/models.py:670
#: core/models.py:630
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:693 core/models.py:693
#: core/models.py:653
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:694 core/models.py:694
#: core/models.py:654
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:700 core/models.py:700
#: core/models.py:660
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:722 core/models.py:722
#: core/models.py:682
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:723 core/models.py:723
#: core/models.py:683
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:729 core/models.py:729
#: core/models.py:689
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:735 core/models.py:735
#: core/models.py:695
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
#: core/models.py:930
#: core/models.py:701 core/models.py:890
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:759 core/models.py:759
#: core/models.py:719
msgid "description"
msgstr ""
#: build/lib/core/models.py:760 core/models.py:760
#: core/models.py:720
msgid "code"
msgstr ""
#: build/lib/core/models.py:761 core/models.py:761
#: core/models.py:721
msgid "css"
msgstr ""
#: build/lib/core/models.py:763 core/models.py:763
#: core/models.py:723
msgid "public"
msgstr ""
#: build/lib/core/models.py:765 core/models.py:765
#: core/models.py:725
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:771 core/models.py:771
#: core/models.py:731
msgid "Template"
msgstr ""
#: build/lib/core/models.py:772 core/models.py:772
#: core/models.py:732
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: core/models.py:871
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:912 core/models.py:912
#: core/models.py:872
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:918 core/models.py:918
#: core/models.py:878
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:924 core/models.py:924
#: core/models.py:884
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:947 core/models.py:947
#: core/models.py:907
msgid "email address"
msgstr ""
#: build/lib/core/models.py:966 core/models.py:966
#: core/models.py:926
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:967 core/models.py:967
#: core/models.py:927
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:987 core/models.py:987
#: core/models.py:944
msgid "This email is already associated to a registered user."
msgstr ""
#: build/lib/impress/settings.py:236 impress/settings.py:236
#: core/templates/mail/html/hello.html:159 core/templates/mail/text/hello.txt:3
msgid "Company logo"
msgstr ""
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
#, python-format
msgid "Hello %(name)s"
msgstr ""
#: core/templates/mail/html/hello.html:188 core/templates/mail/text/hello.txt:5
msgid "Hello"
msgstr ""
#: core/templates/mail/html/hello.html:189 core/templates/mail/text/hello.txt:6
msgid "Thank you very much for your visit!"
msgstr ""
#: core/templates/mail/html/hello.html:221
#, python-format
msgid "This mail has been sent to %(email)s by <a href=\"%(href)s\">%(name)s</a>"
msgstr ""
#: core/templates/mail/html/invitation.html:162
#: core/templates/mail/text/invitation.txt:3
msgid "Logo email"
msgstr ""
#: core/templates/mail/html/invitation.html:209
#: core/templates/mail/text/invitation.txt:10
msgid "Open"
msgstr "Ouvrir"
#: core/templates/mail/html/invitation.html:226
#: core/templates/mail/text/invitation.txt:14
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
msgstr " Docs, votre nouvel outil incontournable pour organiser, partager et collaborer sur vos documents en équipe. "
#: core/templates/mail/html/invitation.html:233
#: core/templates/mail/text/invitation.txt:16
#, python-format
msgid " Brought to you by %(brandname)s "
msgstr " Proposé par %(brandname)s "
#: core/templates/mail/text/hello.txt:8
#, python-format
msgid "This mail has been sent to %(email)s by %(name)s [%(href)s]"
msgstr ""
#: impress/settings.py:236
msgid "English"
msgstr ""
#: build/lib/impress/settings.py:237 impress/settings.py:237
#: impress/settings.py:237
msgid "French"
msgstr ""
#: build/lib/impress/settings.py:238 impress/settings.py:238
#: impress/settings.py:238
msgid "German"
msgstr ""

View File

@@ -1,352 +0,0 @@
msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-15 21:00+0000\n"
"PO-Revision-Date: 2025-01-27 09:27\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: lasuite-docs\n"
"X-Crowdin-Project-ID: 754523\n"
"X-Crowdin-Language: nl\n"
"X-Crowdin-File: backend-impress.pot\n"
"X-Crowdin-File-ID: 18\n"
#: build/lib/core/admin.py:33 core/admin.py:33
msgid "Personal info"
msgstr ""
#: build/lib/core/admin.py:46 core/admin.py:46
msgid "Permissions"
msgstr ""
#: build/lib/core/admin.py:58 core/admin.py:58
msgid "Important dates"
msgstr ""
#: build/lib/core/api/filters.py:16 core/api/filters.py:16
msgid "Creator is me"
msgstr ""
#: build/lib/core/api/filters.py:19 core/api/filters.py:19
msgid "Favorite"
msgstr ""
#: build/lib/core/api/filters.py:22 core/api/filters.py:22
msgid "Title"
msgstr ""
#: build/lib/core/api/serializers.py:317 core/api/serializers.py:317
msgid "A new document was created on your behalf!"
msgstr ""
#: build/lib/core/api/serializers.py:321 core/api/serializers.py:321
msgid "You have been granted ownership of a new document:"
msgstr ""
#: build/lib/core/api/serializers.py:422 core/api/serializers.py:422
msgid "Body"
msgstr ""
#: build/lib/core/api/serializers.py:425 core/api/serializers.py:425
msgid "Body type"
msgstr ""
#: build/lib/core/api/serializers.py:431 core/api/serializers.py:431
msgid "Format"
msgstr ""
#: build/lib/core/authentication/backends.py:61
#: core/authentication/backends.py:61
msgid "Invalid response format or token verification failed"
msgstr ""
#: build/lib/core/authentication/backends.py:108
#: core/authentication/backends.py:108
msgid "User account is disabled"
msgstr ""
#: build/lib/core/models.py:63 build/lib/core/models.py:70 core/models.py:63
#: core/models.py:70
msgid "Reader"
msgstr ""
#: build/lib/core/models.py:64 build/lib/core/models.py:71 core/models.py:64
#: core/models.py:71
msgid "Editor"
msgstr ""
#: build/lib/core/models.py:72 core/models.py:72
msgid "Administrator"
msgstr ""
#: build/lib/core/models.py:73 core/models.py:73
msgid "Owner"
msgstr ""
#: build/lib/core/models.py:84 core/models.py:84
msgid "Restricted"
msgstr ""
#: build/lib/core/models.py:88 core/models.py:88
msgid "Authenticated"
msgstr ""
#: build/lib/core/models.py:90 core/models.py:90
msgid "Public"
msgstr ""
#: build/lib/core/models.py:112 core/models.py:112
msgid "id"
msgstr ""
#: build/lib/core/models.py:113 core/models.py:113
msgid "primary key for the record as UUID"
msgstr ""
#: build/lib/core/models.py:119 core/models.py:119
msgid "created on"
msgstr ""
#: build/lib/core/models.py:120 core/models.py:120
msgid "date and time at which a record was created"
msgstr ""
#: build/lib/core/models.py:125 core/models.py:125
msgid "updated on"
msgstr ""
#: build/lib/core/models.py:126 core/models.py:126
msgid "date and time at which a record was last updated"
msgstr ""
#: build/lib/core/models.py:162 core/models.py:162
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
msgstr ""
#: build/lib/core/models.py:175 core/models.py:175
msgid "Enter a valid sub. This value may contain only letters, numbers, and @/./+/-/_/: characters."
msgstr ""
#: build/lib/core/models.py:181 core/models.py:181
msgid "sub"
msgstr ""
#: build/lib/core/models.py:183 core/models.py:183
msgid "Required. 255 characters or fewer. Letters, numbers, and @/./+/-/_/: characters only."
msgstr ""
#: build/lib/core/models.py:192 core/models.py:192
msgid "full name"
msgstr ""
#: build/lib/core/models.py:193 core/models.py:193
msgid "short name"
msgstr ""
#: build/lib/core/models.py:195 core/models.py:195
msgid "identity email address"
msgstr ""
#: build/lib/core/models.py:200 core/models.py:200
msgid "admin email address"
msgstr ""
#: build/lib/core/models.py:207 core/models.py:207
msgid "language"
msgstr ""
#: build/lib/core/models.py:208 core/models.py:208
msgid "The language in which the user wants to see the interface."
msgstr ""
#: build/lib/core/models.py:214 core/models.py:214
msgid "The timezone in which the user wants to see times."
msgstr ""
#: build/lib/core/models.py:217 core/models.py:217
msgid "device"
msgstr ""
#: build/lib/core/models.py:219 core/models.py:219
msgid "Whether the user is a device or a real user."
msgstr ""
#: build/lib/core/models.py:222 core/models.py:222
msgid "staff status"
msgstr ""
#: build/lib/core/models.py:224 core/models.py:224
msgid "Whether the user can log into this admin site."
msgstr ""
#: build/lib/core/models.py:227 core/models.py:227
msgid "active"
msgstr ""
#: build/lib/core/models.py:230 core/models.py:230
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
msgstr ""
#: build/lib/core/models.py:242 core/models.py:242
msgid "user"
msgstr ""
#: build/lib/core/models.py:243 core/models.py:243
msgid "users"
msgstr ""
#: build/lib/core/models.py:382 build/lib/core/models.py:758 core/models.py:382
#: core/models.py:758
msgid "title"
msgstr ""
#: build/lib/core/models.py:404 core/models.py:404
msgid "Document"
msgstr ""
#: build/lib/core/models.py:405 core/models.py:405
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:408 core/models.py:408
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:633 core/models.py:633
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:637 core/models.py:637
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:640 core/models.py:640
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:663 core/models.py:663
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:664 core/models.py:664
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:670 core/models.py:670
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:693 core/models.py:693
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:694 core/models.py:694
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:700 core/models.py:700
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:722 core/models.py:722
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:723 core/models.py:723
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:729 core/models.py:729
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:735 core/models.py:735
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:741 build/lib/core/models.py:930 core/models.py:741
#: core/models.py:930
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:759 core/models.py:759
msgid "description"
msgstr ""
#: build/lib/core/models.py:760 core/models.py:760
msgid "code"
msgstr ""
#: build/lib/core/models.py:761 core/models.py:761
msgid "css"
msgstr ""
#: build/lib/core/models.py:763 core/models.py:763
msgid "public"
msgstr ""
#: build/lib/core/models.py:765 core/models.py:765
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:771 core/models.py:771
msgid "Template"
msgstr ""
#: build/lib/core/models.py:772 core/models.py:772
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:912 core/models.py:912
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:918 core/models.py:918
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:924 core/models.py:924
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:947 core/models.py:947
msgid "email address"
msgstr ""
#: build/lib/core/models.py:966 core/models.py:966
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:967 core/models.py:967
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:987 core/models.py:987
msgid "This email is already associated to a registered user."
msgstr ""
#: build/lib/impress/settings.py:236 impress/settings.py:236
msgid "English"
msgstr ""
#: build/lib/impress/settings.py:237 impress/settings.py:237
msgid "French"
msgstr ""
#: build/lib/impress/settings.py:238 impress/settings.py:238
msgid "German"
msgstr ""

View File

@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "2.0.1"
version = "2.0.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -37,7 +37,7 @@ dependencies = [
"django-redis==5.4.0",
"django-storages[s3]==1.14.4",
"django-timezone-field>=5.1",
"django==5.1.5",
"django==5.1.4",
"djangorestframework==3.15.2",
"drf_spectacular==0.28.0",
"dockerflow==2024.4.2",
@@ -50,10 +50,13 @@ dependencies = [
"openai==1.58.1",
"psycopg[binary]==3.2.3",
"PyJWT==2.10.1",
"pypandoc==1.14",
"python-frontmatter==1.1.0",
"python-magic==0.4.27",
"requests==2.32.3",
"sentry-sdk==2.19.2",
"url-normalize==1.4.3",
"WeasyPrint>=60.2",
"whitenoise==6.8.2",
"mozilla-django-oidc==4.0.1",
]

View File

@@ -61,6 +61,8 @@ export const addNewMember = async (
role: 'Administrator' | 'Owner' | 'Member' | 'Editor' | 'Reader',
fillText: string = 'user',
) => {
await expect(page.getByTestId('grid-loader')).toBeHidden();
const responsePromiseSearchUser = page.waitForResponse(
(response) =>
response.url().includes(`/users/?q=${fillText}`) &&
@@ -104,7 +106,7 @@ export const goToGridDoc = async (
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(docsGrid.getByTestId('grid-loader')).toBeHidden();
await expect(page.getByTestId('grid-loader')).toBeHidden({ timeout: 10000 });
const rows = docsGrid.getByRole('row');

View File

@@ -16,7 +16,6 @@ const config = {
['de-de', 'German'],
],
LANGUAGE_CODE: 'en-us',
POSTHOG_KEY: {},
SENTRY_DSN: null,
};

View File

@@ -366,27 +366,4 @@ test.describe('Doc Editor', () => {
await expect(editor.getByText('Bonjour le monde')).toBeVisible();
});
test('it checks the multi columns', async ({ page, browserName }) => {
await createDoc(page, 'doc-multi-columns', browserName, 1);
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Three Columns', { exact: true }).click();
await page.locator('.bn-block-column').first().fill('Column 1');
await page.locator('.bn-block-column').nth(1).fill('Column 2');
await page.locator('.bn-block-column').last().fill('Column 3');
expect(await page.locator('.bn-block-column').count()).toBe(3);
await expect(
page.locator('.bn-block-column[data-node-type="column"]').first(),
).toHaveText('Column 1');
await expect(
page.locator('.bn-block-column[data-node-type="column"]').nth(1),
).toHaveText('Column 2');
await expect(
page.locator('.bn-block-column[data-node-type="column"]').last(),
).toHaveText('Column 3');
});
});

View File

@@ -1,7 +1,6 @@
import path from 'path';
import { expect, test } from '@playwright/test';
import cs from 'convert-stream';
import jsdom from 'jsdom';
import pdf from 'pdf-parse';
import { createDoc, verifyDocName } from './common';
@@ -42,8 +41,10 @@ test.describe('Doc Export', () => {
).toBeVisible();
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
});
test('it exports the doc to pdf', async ({ page, browserName }) => {
test('it converts the doc to pdf with a template integrated', async ({
page,
browserName,
}) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => {
@@ -76,7 +77,10 @@ test.describe('Doc Export', () => {
expect(pdfText).toContain('Hello World'); // This is the doc text
});
test('it exports the doc to docx', async ({ page, browserName }) => {
test('it converts the doc to docx with a template integrated', async ({
page,
browserName,
}) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => {
@@ -107,75 +111,152 @@ test.describe('Doc Export', () => {
expect(download.suggestedFilename()).toBe(`${randomDoc}.docx`);
});
/**
* This test tell us that the export to pdf is working with images
* but it does not tell us if the images are beeing displayed correctly
* in the pdf.
*
* TODO: Check if the images are displayed correctly in the pdf
*/
test('it exports the docs with images', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
test('it converts the blocknote json in correct html for the export', async ({
page,
browserName,
}) => {
test.setTimeout(60000);
const fileChooserPromise = page.waitForEvent('filechooser');
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
let body = '';
await page.route('**/templates/*/generate-document/', async (route) => {
const request = route.request();
body = request.postDataJSON().body;
await route.continue();
});
await verifyDocName(page, randomDoc);
await page.locator('.ProseMirror.bn-editor').click();
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
await page.locator('.bn-block-outer').last().fill('Hello World');
await page.locator('.bn-block-outer').last().click();
await page.keyboard.press('Enter');
await page.keyboard.press('Enter');
await page.locator('.bn-block-outer').last().fill('Break');
await expect(page.getByText('Break')).toBeVisible();
// Center the text
await page.getByText('Break').dblclick();
await page.locator('button[data-test="alignTextCenter"]').click();
// Change the background color
await page.locator('button[data-test="colors"]').click();
await page.locator('button[data-test="background-color-brown"]').click();
// Change the text color
await page.getByText('Break').dblclick();
await page.locator('button[data-test="colors"]').click();
await page.locator('button[data-test="text-color-orange"]').click();
// Add a list
await page.locator('.bn-block-outer').last().click();
await page.keyboard.press('Enter');
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Resizable image with caption').click();
await page.getByText('Upload image').click();
await page.getByText('Bullet List').click();
await page
.locator('.bn-block-content[data-content-type="bulletListItem"] p')
.last()
.fill('Test List 1');
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(300);
await page.keyboard.press('Enter');
await page
.locator('.bn-block-content[data-content-type="bulletListItem"] p')
.last()
.fill('Test List 2');
await page.keyboard.press('Enter');
await page
.locator('.bn-block-content[data-content-type="bulletListItem"] p')
.last()
.fill('Test List 3');
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(__dirname, 'assets/logo-suite-numerique.png'),
);
await page.keyboard.press('Enter');
await page.keyboard.press('Backspace');
const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
// Add a number list
await page.locator('.bn-block-outer').last().click();
await page.keyboard.press('Enter');
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Numbered List').click();
await page
.locator('.bn-block-content[data-content-type="numberedListItem"] p')
.last()
.fill('Test Number 1');
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(300);
await page.keyboard.press('Enter');
await page
.locator('.bn-block-content[data-content-type="numberedListItem"] p')
.last()
.fill('Test Number 2');
await page.keyboard.press('Enter');
await page
.locator('.bn-block-content[data-content-type="numberedListItem"] p')
.last()
.fill('Test Number 3');
await expect(image).toBeVisible();
// Add img
await page.locator('.bn-block-outer').last().click();
await page.keyboard.press('Enter');
await page.locator('.bn-block-outer').last().fill('/');
await page
.getByRole('option', {
name: 'Image',
})
.click();
await page
.getByRole('tab', {
name: 'Embed',
})
.click();
await page
.getByPlaceholder('Enter URL')
.fill('https://example.com/image.jpg');
await page
.getByRole('button', {
name: 'Embed image',
})
.click();
// Download
await page
.getByRole('button', {
name: 'download',
})
.click();
await page
.getByRole('combobox', {
name: 'Template',
})
.click();
await page
.getByRole('option', {
name: 'Demo Template',
})
.click({
delay: 100,
});
await new Promise((resolve) => setTimeout(resolve, 1000));
await page
.getByRole('button', {
name: 'Download',
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
// Empty paragraph should be replaced by a <br/>
expect(body.match(/<br>/g)?.length).toBeGreaterThanOrEqual(2);
expect(body).toContain('style="color: orange;"');
expect(body).toContain('custom-style="center"');
expect(body).toContain('style="background-color: brown;"');
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
const pdfExport = await pdf(pdfBuffer);
const pdfText = pdfExport.text;
const { JSDOM } = jsdom;
const DOMParser = new JSDOM().window.DOMParser;
const parser = new DOMParser();
const html = parser.parseFromString(body, 'text/html');
expect(pdfText).toContain('Hello World');
const ulLis = html.querySelectorAll('ul li');
expect(ulLis.length).toBe(3);
expect(ulLis[0].textContent).toBe('Test List 1');
expect(ulLis[1].textContent).toBe('Test List 2');
expect(ulLis[2].textContent).toBe('Test List 3');
const olLis = html.querySelectorAll('ol li');
expect(olLis.length).toBe(3);
expect(olLis[0].textContent).toBe('Test Number 1');
expect(olLis[1].textContent).toBe('Test Number 2');
expect(olLis[2].textContent).toBe('Test Number 3');
const img = html.querySelectorAll('img');
expect(img.length).toBe(1);
expect(img[0].src).toBe('https://example.com/image.jpg');
});
});

View File

@@ -47,7 +47,6 @@ test.describe('Doc Header', () => {
versions_list: true,
versions_retrieve: true,
accesses_manage: true,
accesses_view: true,
update: true,
partial_update: true,
retrieve: true,
@@ -395,38 +394,7 @@ test.describe('Doc Header', () => {
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
expect(clipboardContent.trim()).toBe(
`<h1 data-level=\"1\">Hello World</h1><p></p>`,
);
});
test('it checks the copy link button', async ({ page }) => {
await mockedDocument(page, {
abilities: {
destroy: false, // Means owner
link_configuration: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
accesses_manage: false,
accesses_view: false,
update: true,
partial_update: true,
retrieve: true,
},
});
await goToGridDoc(page);
const shareButton = page.getByRole('button', {
name: 'Share',
exact: true,
});
await expect(shareButton).toBeVisible();
await shareButton.click();
await page.getByRole('button', { name: 'Copy link' }).click();
await expect(page.getByText('Link Copied !')).toBeVisible();
expect(clipboardContent.trim()).toBe(`<h1>Hello World</h1><p></p>`);
});
});
@@ -437,46 +405,6 @@ test.describe('Documents Header mobile', () => {
await page.goto('/');
});
test('it checks the copy link button', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
);
await mockedDocument(page, {
abilities: {
destroy: false,
link_configuration: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
accesses_manage: false,
accesses_view: false,
update: true,
partial_update: true,
retrieve: true,
},
});
await goToGridDoc(page);
await expect(page.getByRole('button', { name: 'Copy link' })).toBeHidden();
await page.getByLabel('Open the document options').click();
await page.getByRole('button', { name: 'Share' }).click();
await page.getByRole('button', { name: 'Copy link' }).click();
await expect(page.getByText('Link Copied !')).toBeVisible();
// Test that clipboard is in HTML format
const handle = await page.evaluateHandle(() =>
navigator.clipboard.readText(),
);
const clipboardContent = await handle.jsonValue();
const origin = await page.evaluate(() => window.location.origin);
expect(clipboardContent.trim()).toMatch(
`${origin}/docs/mocked-document-id/`,
);
});
test('it checks the close button on Share modal', async ({ page }) => {
await mockedDocument(page, {
abilities: {
@@ -486,7 +414,6 @@ test.describe('Documents Header mobile', () => {
versions_list: true,
versions_retrieve: true,
accesses_manage: true,
accesses_view: true,
update: true,
partial_update: true,
retrieve: true,

View File

@@ -250,7 +250,7 @@ test.describe('Doc Visibility: Public', () => {
await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
await expect(page.getByRole('button', { name: 'search' })).toBeHidden();
await expect(page.getByRole('button', { name: 'New doc' })).toBeHidden();
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
const card = page.getByLabel('It is the card information');
await expect(card).toBeVisible();
@@ -314,7 +314,7 @@ test.describe('Doc Visibility: Public', () => {
await page.goto(urlDoc);
await verifyDocName(page, docTitle);
await expect(page.getByRole('button', { name: 'Share' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Share' })).toBeHidden();
});
});
@@ -414,8 +414,13 @@ test.describe('Doc Visibility: Authenticated', () => {
await expect(page.locator('h2').getByText(docTitle)).toBeVisible();
await page.getByRole('button', { name: 'Share' }).click();
await page.getByRole('button', { name: 'Copy link' }).click();
await expect(page.getByText('Link Copied !')).toBeVisible();
await expect(selectVisibility).toBeHidden();
const inputSearch = page.getByRole('combobox', {
name: 'Quick search input',
});
await expect(inputSearch).toBeHidden();
});
test('It checks a authenticated doc in editable mode', async ({
@@ -470,7 +475,12 @@ test.describe('Doc Visibility: Authenticated', () => {
await verifyDocName(page, docTitle);
await page.getByRole('button', { name: 'Share' }).click();
await page.getByRole('button', { name: 'Copy link' }).click();
await expect(page.getByText('Link Copied !')).toBeVisible();
await expect(selectVisibility).toBeHidden();
const inputSearch = page.getByRole('combobox', {
name: 'Quick search input',
});
await expect(inputSearch).toBeHidden();
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "2.0.1",
"version": "2.0.0",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
@@ -22,6 +22,7 @@
},
"dependencies": {
"convert-stream": "1.0.2",
"jsdom": "25.0.1",
"pdf-parse": "1.1.1"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "app-impress",
"version": "2.0.1",
"version": "2.0.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -15,28 +15,22 @@
"test:watch": "jest --watch"
},
"dependencies": {
"@blocknote/core": "0.21.0",
"@blocknote/mantine": "0.21.0",
"@blocknote/react": "0.21.0",
"@blocknote/xl-multi-column": "0.21.0",
"@blocknote/xl-docx-exporter": "0.21.0",
"@blocknote/xl-pdf-exporter": "0.21.0",
"@blocknote/core": "0.22.0",
"@blocknote/mantine": "0.22.0",
"@blocknote/react": "0.22.0",
"@gouvfr-lasuite/integration": "1.0.2",
"@hocuspocus/provider": "2.15.0",
"@openfun/cunningham-react": "2.9.4",
"@react-pdf/renderer": "4.1.6",
"@sentry/nextjs": "8.47.0",
"@tanstack/react-query": "5.62.11",
"cmdk": "1.0.4",
"crisp-sdk-web": "1.0.25",
"docx": "9.1.0",
"i18next": "24.2.0",
"i18next-browser-languagedetector": "8.0.2",
"idb": "8.0.1",
"lodash": "4.17.21",
"luxon": "3.5.0",
"next": "15.1.3",
"posthog-js": "1.204.0",
"react": "*",
"react-aria-components": "1.5.0",
"react-dom": "*",

View File

@@ -20,14 +20,12 @@ export type DropdownMenuProps = {
showArrow?: boolean;
label?: string;
arrowCss?: BoxProps['$css'];
disabled?: boolean;
topMessage?: string;
};
export const DropdownMenu = ({
options,
children,
disabled = false,
showArrow = false,
arrowCss,
label,
@@ -42,10 +40,6 @@ export const DropdownMenu = ({
setIsOpen(isOpen);
};
if (disabled) {
return children;
}
return (
<DropButton
isOpen={isOpen}

View File

@@ -1,8 +1,8 @@
import Link from 'next/link';
import styled, { RuleSet } from 'styled-components';
import styled from 'styled-components';
export interface LinkProps {
$css?: string | RuleSet<object>;
$css?: string;
}
export const StyledLink = styled(Link)<LinkProps>`
@@ -12,5 +12,5 @@ export const StyledLink = styled(Link)<LinkProps>`
color: #ffffff;
}
display: flex;
${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)}
${({ $css }) => $css && `${$css};`}
`;

View File

@@ -3,7 +3,7 @@ import { PropsWithChildren, useEffect } from 'react';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PostHogProvider, configureCrispSession } from '@/services';
import { configureCrispSession } from '@/services';
import { useSentryStore } from '@/stores/useSentryStore';
import { useConfig } from './api/useConfig';
@@ -45,5 +45,5 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
);
}
return <PostHogProvider conf={conf.POSTHOG_KEY}>{children}</PostHogProvider>;
return children;
};

View File

@@ -2,7 +2,6 @@ import { useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Theme } from '@/cunningham/';
import { PostHogConf } from '@/services';
interface ConfigResponse {
LANGUAGES: [string, string][];
@@ -12,7 +11,6 @@ interface ConfigResponse {
CRISP_WEBSITE_ID?: string;
FRONTEND_THEME?: Theme;
MEDIA_BASE_URL?: string;
POSTHOG_KEY?: PostHogConf;
SENTRY_DSN?: string;
}

View File

@@ -576,22 +576,6 @@ input:-webkit-autofill:focus {
}
}
.c__modal__scroller:has(.noPadding) {
padding: 0 !important;
.c__modal__close .c__button {
right: 5px;
top: 5px;
padding: 1.5rem 1rem;
}
.c__modal__title {
font-size: var(--c--theme--font--sizes--xs);
padding: var(--c--theme--spacings--base);
margin-bottom: 0;
}
}
/**
* Toast
*/

View File

@@ -1,26 +1,11 @@
import {
Dictionary,
combineByGroup,
filterSuggestionItems,
locales,
} from '@blocknote/core';
import { Dictionary, locales } from '@blocknote/core';
import '@blocknote/core/fonts/inter.css';
import { BlockNoteView } from '@blocknote/mantine';
import '@blocknote/mantine/style.css';
import {
SuggestionMenuController,
getDefaultReactSlashMenuItems,
useCreateBlockNote,
} from '@blocknote/react';
import {
getMultiColumnSlashMenuItems,
multiColumnDropCursor,
locales as multiColumnLocales,
} from '@blocknote/xl-multi-column';
import { useCreateBlockNote } from '@blocknote/react';
import { HocuspocusProvider } from '@hocuspocus/provider';
import { useEffect, useMemo } from 'react';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import * as Y from 'yjs';
import { Box, TextErrors } from '@/components';
@@ -31,23 +16,21 @@ import { useUploadFile } from '../hook';
import { useHeadings } from '../hook/useHeadings';
import useSaveDoc from '../hook/useSaveDoc';
import { useEditorStore } from '../stores';
import { blockNoteWithMultiColumn, randomColor } from '../utils';
import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
const cssEditor = (readonly: boolean) => css`
&,
& > .bn-container,
& .ProseMirror {
height: 100%;
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 50px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
height: 43px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
const cssEditor = (readonly: boolean) => `
&, & > .bn-container, & .ProseMirror {
height:100%;
.bn-side-menu[data-block-type=heading][data-level="1"] {
height: 50px;
}
.bn-side-menu[data-block-type=heading][data-level="2"] {
height: 43px;
}
.bn-side-menu[data-block-type=heading][data-level="3"] {
height: 35px;
}
h1 {
@@ -69,11 +52,11 @@ const cssEditor = (readonly: boolean) => css`
border-left: none;
}
}
.bn-editor {
color: var(--c--theme--colors--greyscale-700);
}
.bn-block-outer:not(:first-child) {
&:has(h1) {
padding-top: 32px;
@@ -84,25 +67,25 @@ const cssEditor = (readonly: boolean) => css`
&:has(h3) {
padding-top: 16px;
}
}
};
& .bn-inline-content code {
background-color: gainsboro;
padding: 2px;
border-radius: 4px;
}
@media screen and (width <= 560px) {
& .bn-editor {
${readonly && `padding-left: 10px;`}
};
.bn-side-menu[data-block-type=heading][data-level="1"] {
height: 46px;
}
.bn-side-menu[data-block-type='heading'][data-level='1'] {
height: 46px;
}
.bn-side-menu[data-block-type='heading'][data-level='2'] {
.bn-side-menu[data-block-type=heading][data-level="2"] {
height: 40px;
}
.bn-side-menu[data-block-type='heading'][data-level='3'] {
.bn-side-menu[data-block-type=heading][data-level="3"] {
height: 40px;
}
& .bn-editor h1 {
@@ -114,7 +97,7 @@ const cssEditor = (readonly: boolean) => css`
& .bn-editor h3 {
font-size: 1.2rem;
}
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
.bn-block-content[data-is-empty-and-focused][data-content-type="paragraph"]
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
font-size: 14px;
}
@@ -177,14 +160,8 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
return cursor;
},
},
dictionary: {
...(locales[lang as keyof typeof locales] as Dictionary),
multi_column:
multiColumnLocales[lang as keyof typeof multiColumnLocales],
},
dictionary: locales[lang as keyof typeof locales] as Dictionary,
uploadFile,
schema: blockNoteWithMultiColumn,
dropCursor: multiColumnDropCursor,
},
[collabName, lang, provider, uploadFile],
);
@@ -198,24 +175,8 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
};
}, [setEditor, editor]);
const getSlashMenuItems = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/require-await
return async (query: string) =>
filterSuggestionItems(
combineByGroup(
getDefaultReactSlashMenuItems(editor),
getMultiColumnSlashMenuItems(editor),
),
query,
);
}, [editor]);
return (
<Box
$padding={{ top: 'md' }}
$background="white"
$css={cssEditor(readOnly)}
>
<Box $css={cssEditor(readOnly)}>
{errorAttachment && (
<Box $margin={{ bottom: 'big' }}>
<TextErrors
@@ -231,12 +192,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
formattingToolbar={false}
editable={!readOnly}
theme="light"
slashMenu={false}
>
<SuggestionMenuController
triggerCharacter="/"
getItems={getSlashMenuItems}
/>
<BlockNoteToolbar />
</BlockNoteView>
</Box>
@@ -262,7 +218,6 @@ export const BlockNoteEditorVersion = ({
},
provider: undefined,
},
schema: blockNoteWithMultiColumn,
},
[initialContent],
);

View File

@@ -1,9 +1,9 @@
import { BlockNoteEditor } from '@blocknote/core';
import { useEffect } from 'react';
import { useHeadingStore } from '../stores';
import { DocsBlockNoteEditor } from '../types';
export const useHeadings = (editor: DocsBlockNoteEditor) => {
export const useHeadings = (editor: BlockNoteEditor) => {
const { setHeadings, resetHeadings } = useHeadingStore();
useEffect(() => {

View File

@@ -1,10 +1,9 @@
import { BlockNoteEditor } from '@blocknote/core';
import { create } from 'zustand';
import { DocsBlockNoteEditor } from '../types';
export interface UseEditorstore {
editor?: DocsBlockNoteEditor;
setEditor: (editor: DocsBlockNoteEditor | undefined) => void;
editor?: BlockNoteEditor;
setEditor: (editor: BlockNoteEditor | undefined) => void;
}
export const useEditorStore = create<UseEditorstore>((set) => ({

View File

@@ -1,6 +1,7 @@
import { BlockNoteEditor } from '@blocknote/core';
import { create } from 'zustand';
import { DocsBlockNoteEditor, HeadingBlock } from '../types';
import { HeadingBlock } from '../types';
const recursiveTextContent = (content: HeadingBlock['content']): string => {
if (!content) {
@@ -20,7 +21,7 @@ const recursiveTextContent = (content: HeadingBlock['content']): string => {
export interface UseHeadingStore {
headings: HeadingBlock[];
setHeadings: (editor: DocsBlockNoteEditor) => void;
setHeadings: (editor: BlockNoteEditor) => void;
resetHeadings: () => void;
}

View File

@@ -1,7 +1,3 @@
import { BlockNoteEditor } from '@blocknote/core';
import { blockNoteWithMultiColumn } from './utils';
export interface DocAttachment {
file: string;
}
@@ -16,9 +12,3 @@ export type HeadingBlock = {
level: number;
};
};
export type DocsBlockNoteEditor = BlockNoteEditor<
typeof blockNoteWithMultiColumn.blockSchema,
typeof blockNoteWithMultiColumn.inlineContentSchema,
typeof blockNoteWithMultiColumn.styleSchema
>;

View File

@@ -1,6 +1,3 @@
import { BlockNoteSchema } from '@blocknote/core';
import { withMultiColumn } from '@blocknote/xl-multi-column';
export const randomColor = () => {
const randomInt = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
@@ -28,7 +25,3 @@ function hslToHex(h: number, s: number, l: number) {
export const toBase64 = (str: Uint8Array) =>
Buffer.from(str).toString('base64');
export const blockNoteWithMultiColumn = withMultiColumn(
BlockNoteSchema.create(),
);

View File

@@ -42,7 +42,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
<Box
aria-label={t('Public document')}
$color={colors['primary-800']}
$background={colors['primary-050']}
$background={colors['primary-100']}
$radius={spacings['3xs']}
$direction="row"
$padding="xs"
@@ -64,12 +64,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
</Text>
</Box>
)}
<Box
$direction="row"
$align="center"
$width="100%"
$padding={{ bottom: 'xs' }}
>
<Box $direction="row" $align="center" $width="100%">
<Box
$direction="row"
$justify="space-between"
@@ -103,7 +98,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
<DocToolBox doc={doc} />
</Box>
</Box>
<HorizontalSeparator $withPadding={false} />
<HorizontalSeparator $withPadding={true} />
</Box>
</>
);

View File

@@ -16,6 +16,7 @@ import {
Icon,
IconOptions,
} from '@/components';
import { useAuthStore } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { useEditorStore } from '@/features/docs/doc-editor/';
import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management';
@@ -26,7 +27,7 @@ import {
} from '@/features/docs/doc-versioning';
import { useResponsiveStore } from '@/stores';
import { ModalExport } from './ModalExport';
import { ModalPDF } from './ModalExport';
interface DocToolBoxProps {
doc: Doc;
@@ -36,18 +37,18 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const { t } = useTranslation();
const hasAccesses = doc.nb_accesses > 1;
const queryClient = useQueryClient();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const colors = colorsTokens();
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
const selectHistoryModal = useModal();
const modalShare = useModal();
const { isSmallMobile, isDesktop } = useResponsiveStore();
const { authenticated } = useAuthStore();
const { editor } = useEditorStore();
const { toast } = useToastProvider();
@@ -56,14 +57,16 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
? [
{
label: t('Share'),
icon: 'group',
callback: modalShare.open,
icon: 'upload',
callback: () => {
modalShare.open();
},
},
{
label: t('Export'),
icon: 'download',
callback: () => {
setIsModalExportOpen(true);
setIsModalPDFOpen(true);
},
},
]
@@ -150,7 +153,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
$margin={{ left: 'auto' }}
$gap={spacings['2xs']}
>
{!isSmallMobile && (
{authenticated && !isSmallMobile && (
<>
{!hasAccesses && (
<Button
@@ -190,7 +193,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
)}
</>
)}
{!isSmallMobile && (
<Button
color="tertiary-text"
@@ -198,7 +200,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
<Icon iconName="download" $theme="primary" $variation="800" />
}
onClick={() => {
setIsModalExportOpen(true);
setIsModalPDFOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
/>
@@ -228,8 +230,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
{modalShare.isOpen && (
<DocShareModal onClose={() => modalShare.close()} doc={doc} />
)}
{isModalExportOpen && (
<ModalExport onClose={() => setIsModalExportOpen(false)} doc={doc} />
{isModalPDFOpen && (
<ModalPDF onClose={() => setIsModalPDFOpen(false)} doc={doc} />
)}
{isModalRemoveOpen && (
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />

View File

@@ -1,11 +1,3 @@
import {
DOCXExporter,
docxDefaultSchemaMappings,
} from '@blocknote/xl-docx-exporter';
import {
PDFExporter,
pdfDefaultSchemaMappings,
} from '@blocknote/xl-pdf-exporter';
import {
Button,
Loader,
@@ -15,116 +7,91 @@ import {
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { pdf } from '@react-pdf/renderer';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Text } from '@/components';
import { useEditorStore } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { useExport } from '../api/useExport';
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
import { downloadFile, exportResolveFileUrl } from '../utils';
import { adaptBlockNoteHTML, downloadFile } from '../utils';
enum DocDownloadFormat {
export enum DocDownloadFormat {
PDF = 'pdf',
DOCX = 'docx',
}
interface ModalExportProps {
interface ModalPDFProps {
onClose: () => void;
doc: Doc;
}
export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
const { t } = useTranslation();
const { data: templates } = useTemplates({
ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
});
const { toast } = useToastProvider();
const { editor } = useEditorStore();
const [templateSelected, setTemplateSelected] = useState<string>('');
const [isExporting, setIsExporting] = useState(false);
const {
mutate: createExport,
data: documentGenerated,
isSuccess,
isPending,
error,
} = useExport();
const [templateIdSelected, setTemplateIdSelected] = useState<string>();
const [format, setFormat] = useState<DocDownloadFormat>(
DocDownloadFormat.PDF,
);
const templateOptions = useMemo(() => {
const templateOptions = (templates?.pages || [])
if (!templates?.pages) {
return [];
}
const templateOptions = templates.pages
.map((page) =>
page.results.map((template) => ({
label: template.title,
value: template.code,
value: template.id,
})),
)
.flat();
templateOptions.unshift({
label: t('Empty template'),
value: '',
});
if (templateOptions.length) {
setTemplateIdSelected(templateOptions[0].value);
}
return templateOptions;
}, [t, templates?.pages]);
}, [templates?.pages]);
async function onSubmit() {
if (!editor) {
toast(t('The export failed'), VariantType.ERROR);
useEffect(() => {
if (!error) {
return;
}
setIsExporting(true);
toast(error.message, VariantType.ERROR);
onClose();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [error, t]);
useEffect(() => {
if (!documentGenerated || !isSuccess) {
return;
}
// normalize title
const title = doc.title
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/\s/g, '-');
const html = templateSelected;
let exportDocument = editor.document;
if (html) {
const blockTemplate = await editor.tryParseHTMLToBlocks(html);
exportDocument = [...blockTemplate, ...editor.document];
}
let blobExport: Blob;
if (format === DocDownloadFormat.PDF) {
const defaultExporter = new PDFExporter(
editor.schema,
pdfDefaultSchemaMappings,
);
const exporter = new PDFExporter(
editor.schema,
pdfDefaultSchemaMappings,
{
resolveFileUrl: async (url) =>
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
},
);
const pdfDocument = await exporter.toReactPDFDocument(exportDocument);
blobExport = await pdf(pdfDocument).toBlob();
} else {
const defaultExporter = new DOCXExporter(
editor.schema,
docxDefaultSchemaMappings,
);
const exporter = new DOCXExporter(
editor.schema,
docxDefaultSchemaMappings,
{
resolveFileUrl: async (url) =>
exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
},
);
blobExport = await exporter.toBlob(exportDocument);
}
downloadFile(blobExport, `${title}.${format}`);
downloadFile(documentGenerated, `${title}.${format}`);
toast(
t('Your {{format}} was downloaded succesfully', {
@@ -133,9 +100,29 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
VariantType.SUCCESS,
);
setIsExporting(false);
onClose();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [documentGenerated, isSuccess, t]);
async function onSubmit() {
if (!templateIdSelected || !format) {
return;
}
if (!editor) {
toast(t('No editor found'), VariantType.ERROR);
return;
}
let body = await editor.blocksToFullHTML(editor.document);
body = adaptBlockNoteHTML(body);
createExport({
templateId: templateIdSelected,
body,
body_type: 'html',
format,
});
}
return (
@@ -151,7 +138,6 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
color="secondary"
fullWidth
onClick={() => onClose()}
disabled={isExporting}
>
{t('Cancel')}
</Button>
@@ -160,7 +146,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
color="primary"
fullWidth
onClick={() => void onSubmit()}
disabled={isExporting}
disabled={isPending || !templateIdSelected}
>
{t('Download')}
</Button>
@@ -187,9 +173,9 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
clearable={false}
label={t('Template')}
options={templateOptions}
value={templateSelected}
value={templateIdSelected}
onChange={(options) =>
setTemplateSelected(options.target.value as string)
setTemplateIdSelected(options.target.value as string)
}
/>
<Select
@@ -206,17 +192,8 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
}
/>
{isExporting && (
<Box
$align="center"
$margin={{ top: 'big' }}
$css={css`
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -100%);
`}
>
{isPending && (
<Box $align="center" $margin={{ top: 'big' }}>
<Loader />
</Box>
)}

View File

@@ -10,23 +10,137 @@ export function downloadFile(blob: Blob, filename: string) {
window.URL.revokeObjectURL(url);
}
export const exportResolveFileUrl = async (
url: string,
resolveFileUrl: ((url: string) => Promise<string | Blob>) | undefined,
) => {
if (!url.includes(window.location.hostname) && resolveFileUrl) {
return resolveFileUrl(url);
}
const convertToLi = (html: string) => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const divs = doc.querySelectorAll(
'div[data-content-type="bulletListItem"] , div[data-content-type="numberedListItem"]',
);
try {
const response = await fetch(url, {
credentials: 'include',
// Loop through each div and replace it with a li
divs.forEach((div) => {
// Create a new li element
const li = document.createElement('li');
// Copy the attributes from the div to the li
for (let i = 0; i < div.attributes.length; i++) {
li.setAttribute(div.attributes[i].name, div.attributes[i].value);
}
// Move all child elements of the div to the li
while (div.firstChild) {
li.appendChild(div.firstChild);
}
// Replace the div with the li in the DOM
if (div.parentNode) {
div.parentNode.replaceChild(li, div);
}
});
/**
* Convert the blocknote content to a simplified version to be
* correctly parsed by our pdf and docx parser
*/
const newContent: string[] = [];
let currentList: HTMLUListElement | HTMLOListElement | null = null;
// Iterate over all the children of the bn-block-group
doc.body
.querySelectorAll('.bn-block-group .bn-block-outer')
.forEach((outerDiv) => {
const blockContent = outerDiv.querySelector('.bn-block-content');
if (blockContent) {
const contentType = blockContent.getAttribute('data-content-type');
if (contentType === 'bulletListItem') {
// If a list is not started, start a new one
if (!currentList) {
currentList = document.createElement('ul');
}
currentList.appendChild(blockContent);
} else if (contentType === 'numberedListItem') {
// If a numbered list is not started, start a new one
if (!currentList) {
currentList = document.createElement('ol');
}
currentList.appendChild(blockContent);
} else {
/***
* If there is a current list, add it to the new content
* It means that the current list has ended
*/
if (currentList) {
newContent.push(currentList.outerHTML);
}
currentList = null;
newContent.push(outerDiv.outerHTML);
}
} else {
// In case there is no content-type, add the outerDiv as is
newContent.push(outerDiv.outerHTML);
}
});
return response.blob();
} catch {
console.error(`Failed to fetch image: ${url}`);
}
return url;
return newContent.join('');
};
const convertToImg = (html: string) => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const divs = doc.querySelectorAll('div[data-content-type="image"]');
// Loop through each div and replace it with a img
divs.forEach((div) => {
const img = document.createElement('img');
// Copy the attributes from the div to the img
for (let i = 0; i < div.attributes.length; i++) {
img.setAttribute(div.attributes[i].name, div.attributes[i].value);
if (div.attributes[i].name === 'data-url') {
img.setAttribute('src', div.attributes[i].value);
}
if (div.attributes[i].name === 'data-preview-width') {
img.setAttribute('width', div.attributes[i].value);
}
}
// Move all child elements of the div to the img
while (div.firstChild) {
img.appendChild(div.firstChild);
}
// Replace the div with the img in the DOM
if (div.parentNode) {
div.parentNode.replaceChild(img, div);
}
});
return doc.body.innerHTML;
};
export const adaptBlockNoteHTML = (html: string) => {
html = html.replaceAll('<p class="bn-inline-content"></p>', '<br/>');
// custom-style is used by pandoc to convert the style
html = html.replaceAll(
/data-text-alignment=\"([a-z]+)\"/g,
'custom-style="$1"',
);
html = html.replaceAll(/data-text-color=\"([a-z]+)\"/g, 'style="color: $1;"');
html = html.replaceAll(
/data-background-color=\"([a-z]+)\"/g,
'style="background-color: $1;"',
);
html = convertToLi(html);
html = convertToImg(html);
return html;
};

View File

@@ -1,3 +1,2 @@
export * from './useCollaboration';
export * from './useTrans';
export * from './useCopyDocLink';

View File

@@ -1,19 +0,0 @@
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useClipboard } from '@/hook';
import { Doc } from '../types';
export const useCopyDocLink = (docId: Doc['id']) => {
const { t } = useTranslation();
const copyToClipboard = useClipboard();
return useCallback(() => {
copyToClipboard(
`${window.location.origin}/docs/${docId}/`,
t('Link Copied !'),
t('Failed to copy link'),
);
}, [copyToClipboard, docId, t]);
};

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { createGlobalStyle, css } from 'styled-components';
import { useDebouncedCallback } from 'use-debounce';
import { Box, HorizontalSeparator, LoadMoreText, Text } from '@/components';
import { Box, LoadMoreText } from '@/components';
import {
QuickSearch,
QuickSearchData,
@@ -58,7 +58,6 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
const [listHeight, setListHeight] = useState<string>('400px');
const canShare = doc.abilities.accesses_manage;
const canViewAccesses = doc.abilities.accesses_view;
const showMemberSection = inputValue === '' && selectedUsers.length === 0;
const showFooter = selectedUsers.length === 0 && !inputValue;
@@ -95,7 +94,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
count === 1
? t('Document owner')
: t('Share with {{count}} users', {
count: count,
count,
}),
elements: members,
endActions: membersQuery.hasNextPage
@@ -192,9 +191,8 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
<ShareModalStyle />
<Box
aria-label={t('Share modal')}
$height={canViewAccesses ? modalContentHeight : 'auto'}
$height={modalContentHeight}
$overflow="hidden"
className="noPadding"
$justify="space-between"
>
<Box
@@ -206,7 +204,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
}
`}
>
<Box ref={selectedUsersRef}>
<div ref={selectedUsersRef}>
{canShare && selectedUsers.length > 0 && (
<Box
$padding={{ horizontal: 'base' }}
@@ -224,76 +222,55 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
/>
</Box>
)}
{!canViewAccesses && <HorizontalSeparator />}
</Box>
</div>
<Box data-testid="doc-share-quick-search">
{!canViewAccesses && (
<Box $height={listHeight} $align="center" $justify="center">
<Text
$maxWidth="320px"
$textAlign="center"
$variation="600"
$size="sm"
>
{t(
'You do not have permission to view users sharing this document or modify link settings.',
<QuickSearch
onFilter={(str) => {
setInputValue(str);
onFilter(str);
}}
inputValue={inputValue}
showInput={canShare}
loading={searchUsersQuery.isLoading}
placeholder={t('Type a name or email')}
>
{!showMemberSection && inputValue !== '' && (
<QuickSearchGroup
group={searchUserData}
onSelect={onSelect}
renderElement={(user) => (
<DocShareModalInviteUserRow user={user} />
)}
</Text>
</Box>
)}
{canViewAccesses && (
<QuickSearch
onFilter={(str) => {
setInputValue(str);
onFilter(str);
}}
inputValue={inputValue}
showInput={canShare}
loading={searchUsersQuery.isLoading}
placeholder={t('Type a name or email')}
>
{canViewAccesses && (
<>
{!showMemberSection && inputValue !== '' && (
/>
)}
{showMemberSection && (
<>
{invitationsData.elements.length > 0 && (
<Box aria-label={t('List invitation card')}>
<QuickSearchGroup
group={searchUserData}
onSelect={onSelect}
renderElement={(user) => (
<DocShareModalInviteUserRow user={user} />
group={invitationsData}
renderElement={(invitation) => (
<DocShareInvitationItem
doc={doc}
invitation={invitation}
/>
)}
/>
)}
{showMemberSection && (
<>
{invitationsData.elements.length > 0 && (
<Box aria-label={t('List invitation card')}>
<QuickSearchGroup
group={invitationsData}
renderElement={(invitation) => (
<DocShareInvitationItem
doc={doc}
invitation={invitation}
/>
)}
/>
</Box>
)}
</Box>
)}
<Box aria-label={t('List members card')}>
<QuickSearchGroup
group={membersData}
renderElement={(access) => (
<DocShareMemberItem doc={doc} access={access} />
)}
/>
</Box>
</>
)}
</>
)}
</QuickSearch>
)}
<Box aria-label={t('List members card')}>
<QuickSearchGroup
group={membersData}
renderElement={(access) => (
<DocShareMemberItem doc={doc} access={access} />
)}
/>
</Box>
</>
)}
</QuickSearch>
</Box>
</Box>

View File

@@ -1,9 +1,13 @@
import { Button } from '@openfun/cunningham-react';
import {
Button,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, HorizontalSeparator } from '@/components';
import { Doc, useCopyDocLink } from '@/features/docs';
import { Doc } from '@/features/docs';
import { DocVisibility } from './DocVisibility';
@@ -13,7 +17,8 @@ type Props = {
};
export const DocShareModalFooter = ({ doc, onClose }: Props) => {
const copyDocLink = useCopyDocLink(doc.id);
const canShare = doc.abilities.accesses_manage;
const { toast } = useToastProvider();
const { t } = useTranslation();
return (
<Box
@@ -22,10 +27,12 @@ export const DocShareModalFooter = ({ doc, onClose }: Props) => {
`}
>
<HorizontalSeparator $withPadding={true} />
<DocVisibility doc={doc} />
<HorizontalSeparator />
{canShare && (
<>
<DocVisibility doc={doc} />
<HorizontalSeparator />
</>
)}
<Box
$direction="row"
$justify="space-between"
@@ -34,7 +41,18 @@ export const DocShareModalFooter = ({ doc, onClose }: Props) => {
<Button
fullWidth={false}
onClick={() => {
copyDocLink();
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast(t('Link Copied !'), VariantType.SUCCESS, {
duration: 3000,
});
})
.catch(() => {
toast(t('Failed to copy link'), VariantType.ERROR, {
duration: 3000,
});
});
}}
color="tertiary"
icon={<span className="material-icons">add_link</span>}

View File

@@ -26,10 +26,10 @@ export const DocShareModalInviteUserRow = ({ user }: Props) => {
color: var(--c--theme--colors--greyscale-400);
`}
>
<Text $theme="primary" $variation="800">
<Text $theme="primary" $variation="600">
{t('Add')}
</Text>
<Icon $theme="primary" $variation="800" iconName="add" />
<Icon $theme="primary" $variation="600" iconName="add" />
</Box>
}
/>

View File

@@ -34,7 +34,6 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const spacing = spacingsTokens();
const colors = colorsTokens();
const canManage = doc.abilities.accesses_manage;
const [linkReach, setLinkReach] = useState<LinkReach>(doc.link_reach);
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(doc.link_role);
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
@@ -107,35 +106,29 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
$direction="row"
$align={isDesktop ? 'center' : undefined}
$padding={{ horizontal: '2xs' }}
$gap={canManage ? spacing['3xs'] : spacing['base']}
$gap={spacing['3xs']}
>
<DropdownMenu
label={t('Visibility')}
arrowCss={css`
color: ${colors['primary-800']} !important;
`}
disabled={!canManage}
showArrow={true}
options={linkReachOptions}
>
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
<Icon
$theme={canManage ? 'primary' : 'greyscale'}
$variation={canManage ? '800' : '600'}
$theme="primary"
$variation="800"
iconName={linkReachChoices[linkReach].icon}
/>
<Text
$theme={canManage ? 'primary' : 'greyscale'}
$variation={canManage ? '800' : '600'}
$weight="500"
$size="md"
>
<Text $theme="primary" $variation="800">
{linkReachChoices[linkReach].label}
</Text>
</Box>
</DropdownMenu>
{isDesktop && (
<Text $size="xs" $variation="600" $weight="400">
<Text $size="xs" $variation="600">
{description}
</Text>
)}
@@ -144,7 +137,6 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
{linkReach !== LinkReach.RESTRICTED && (
<DropdownMenu
disabled={!canManage}
showArrow={true}
options={linkMode}
label={t('Visibility mode')}

View File

@@ -26,7 +26,7 @@ export const useTranslatedShareSettings = () => {
},
[LinkReach.AUTHENTICATED]: {
label: linkReachTranslations[LinkReach.AUTHENTICATED],
icon: 'vpn_lock',
icon: 'corporate_fare',
value: LinkReach.AUTHENTICATED,
descriptionReadOnly: t(
'Anyone with the link can view the document if they are logged in',

View File

@@ -1,14 +1,14 @@
import { BlockNoteEditor } from '@blocknote/core';
import { useState } from 'react';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocsBlockNoteEditor } from '@/features/docs/doc-editor';
import { useResponsiveStore } from '@/stores';
const leftPaddingMap: { [key: number]: string } = {
3: '1.5rem',
2: '0.9rem',
1: '0.3rem',
1: '0.3',
};
export type HeadingsHighlight = {
@@ -17,7 +17,7 @@ export type HeadingsHighlight = {
}[];
interface HeadingProps {
editor: DocsBlockNoteEditor;
editor: BlockNoteEditor;
level: number;
text: string;
headingId: string;

View File

@@ -10,8 +10,6 @@ import {
} from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
import { DocsGridItem } from './DocsGridItem';
import { DocsGridLoader } from './DocsGridLoader';
@@ -24,7 +22,6 @@ export const DocsGrid = ({
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { flexLeft, flexRight } = useResponsiveDocGrid();
const {
data,
@@ -104,21 +101,23 @@ export const DocsGrid = ({
<Box
$direction="row"
$padding={{ horizontal: 'xs' }}
$gap="10px"
$gap="20px"
data-testid="docs-grid-header"
>
<Box $flex={flexLeft} $padding="3xs">
<Box $flex={6} $padding="3xs">
<Text $size="xs" $variation="600" $weight="500">
{t('Name')}
</Text>
</Box>
{isDesktop && (
<Box $flex={flexRight} $padding={{ vertical: '3xs' }}>
<Box $flex={2} $padding="3xs">
<Text $size="xs" $weight="500" $variation="600">
{t('Updated at')}
</Text>
</Box>
)}
<Box $flex={1.15} $align="flex-end" $padding="3xs" />
</Box>
{/* Body */}

View File

@@ -20,7 +20,6 @@ export const DocsGridActions = ({
openShareModal,
}: DocsGridActionsProps) => {
const { t } = useTranslation();
const deleteModal = useModal();
const removeFavoriteDoc = useDeleteFavoriteDoc({
@@ -46,10 +45,7 @@ export const DocsGridActions = ({
{
label: t('Share'),
icon: 'group',
callback: () => {
openShareModal?.();
},
callback: () => openShareModal?.(),
testId: `docs-grid-actions-share-${doc.id}`,
},

View File

@@ -1,33 +1,27 @@
import { Tooltip, useModal } from '@openfun/cunningham-react';
import { Button, useModal } from '@openfun/cunningham-react';
import { DateTime } from 'luxon';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Icon, StyledLink, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, LinkReach } from '@/features/docs/doc-management';
import { DocShareModal } from '@/features/docs/doc-share';
import { useResponsiveStore } from '@/stores';
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
import { DocsGridActions } from './DocsGridActions';
import { DocsGridItemSharedButton } from './DocsGridItemSharedButton';
import { SimpleDocItem } from './SimpleDocItem';
type DocsGridItemProps = {
doc: Doc;
};
export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { flexLeft, flexRight } = useResponsiveDocGrid();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const shareModal = useModal();
const isPublic = doc.link_reach === LinkReach.PUBLIC;
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
const showAccesses = isPublic || isAuthenticated;
const isRestricted = doc.link_reach === LinkReach.RESTRICTED;
const sharedCount = doc.nb_accesses - 1;
const isShared = sharedCount > 0;
const handleShareClick = () => {
shareModal.open();
@@ -39,8 +33,8 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
$direction="row"
$width="100%"
$align="center"
role="row"
$gap="20px"
role="row"
$padding={{ vertical: '2xs', horizontal: isDesktop ? 'base' : 'xs' }}
$css={css`
cursor: pointer;
@@ -51,80 +45,75 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
`}
>
<StyledLink
$css={css`
flex: ${flexLeft};
align-items: center;
`}
$css="flex: 8; align-items: center;"
href={`/docs/${doc.id}`}
>
<Box
data-testid={`docs-grid-name-${doc.id}`}
$direction="row"
$align="center"
$gap={spacings.xs}
$flex={flexLeft}
$padding={{ right: isDesktop ? 'md' : '3xs' }}
$flex={6}
$padding={{ right: 'base' }}
>
<SimpleDocItem isPinned={doc.is_favorite} doc={doc} />
{showAccesses && (
<Box
$padding={{ top: !isDesktop ? '4xs' : undefined }}
$css={
!isDesktop
? css`
align-self: flex-start;
`
: undefined
}
>
<Tooltip
content={
<Text $textAlign="center" $variation="000">
{isPublic
? t('Accessible to anyone')
: t('Accessible to authenticated users')}
</Text>
}
placement="top"
>
<div>
<Icon
$theme="greyscale"
$variation="600"
$size="14px"
iconName={isPublic ? 'public' : 'vpn_lock'}
/>
</div>
</Tooltip>
</Box>
)}
</Box>
</StyledLink>
<Box
$flex={flexRight}
$direction="row"
$align="center"
$justify={isDesktop ? 'space-between' : 'flex-end'}
$gap="32px"
>
{isDesktop && (
<StyledLink href={`/docs/${doc.id}`}>
<Box $flex={2}>
<Text $variation="600" $size="xs">
{DateTime.fromISO(doc.updated_at).toRelative()}
</Text>
</StyledLink>
</Box>
)}
<Box $direction="row" $align="center" $gap={spacings.lg}>
{isDesktop && (
<DocsGridItemSharedButton
doc={doc}
handleClick={handleShareClick}
/>
)}
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
</Box>
</StyledLink>
<Box
$flex={1.15}
$direction="row"
$align="center"
$justify="flex-end"
$gap="32px"
>
{isDesktop && isPublic && (
<Button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShareClick();
}}
size="nano"
fullWidth
icon={<Icon $variation="000" iconName="public" />}
>
{isShared ? sharedCount : undefined}
</Button>
)}
{isDesktop && !isPublic && isRestricted && isShared && (
<Button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShareClick();
}}
fullWidth
color="tertiary"
size="nano"
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
>
{sharedCount}
</Button>
)}
{isDesktop && !isPublic && isAuthenticated && (
<Button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShareClick();
}}
fullWidth
size="nano"
icon={<Icon $variation="000" iconName="corporate_fare" />}
>
{sharedCount}
</Button>
)}
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
</Box>
</Box>
{shareModal.isOpen && (

View File

@@ -1,45 +0,0 @@
import { Button, Tooltip } from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next';
import { Box, Icon, Text } from '@/components';
import { Doc } from '../../doc-management';
type Props = {
doc: Doc;
handleClick: () => void;
};
export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
const { t } = useTranslation();
const sharedCount = doc.nb_accesses;
const isShared = sharedCount - 1 > 0;
if (!isShared) {
return <Box $minWidth="50px">&nbsp;</Box>;
}
return (
<Tooltip
content={
<Text $textAlign="center" $variation="000">
{t('Shared with {{count}} users', { count: sharedCount })}
</Text>
}
placement="top"
>
<Button
style={{ minWidth: '50px', justifyContent: 'center' }}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleClick();
}}
color="tertiary"
size="nano"
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
>
{sharedCount}
</Button>
</Tooltip>
);
};

View File

@@ -1,9 +1,9 @@
import { DateTime } from 'luxon';
import { css } from 'styled-components';
import { Box, Text } from '@/components';
import { Box, Icon, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc } from '@/features/docs/doc-management';
import { Doc, LinkReach } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import PinnedDocumentIcon from '../assets/pinned-document.svg';
@@ -34,6 +34,11 @@ export const SimpleDocItem = ({
const { isDesktop } = useResponsiveStore();
const spacings = spacingsTokens();
const isPublic = doc?.link_reach === LinkReach.PUBLIC;
const isShared = !isPublic && doc.nb_accesses > 1;
const accessCount = doc.nb_accesses - 1;
const isSharedOrPublic = isShared || isPublic;
return (
<Box $direction="row" $gap={spacings.sm}>
<Box
@@ -64,6 +69,22 @@ export const SimpleDocItem = ({
$gap={spacings['3xs']}
$margin={{ top: '-2px' }}
>
{isPublic && (
<Icon iconName="public" $size="16px" $variation="600" />
)}
{isShared && (
<Icon iconName="group" $size="16px" $variation="600" />
)}
{isSharedOrPublic && accessCount > 0 && (
<Text $size="12px" $weight="bold" $variation="600">
{accessCount}
</Text>
)}
{isSharedOrPublic && (
<Text $size="12px" $variation="600">
·
</Text>
)}
<Text $variation="600" $size="xs">
{DateTime.fromISO(doc.updated_at).toRelative()}
</Text>

View File

@@ -1,29 +0,0 @@
import { useMemo } from 'react';
import { useResponsiveStore } from '@/stores';
export const useResponsiveDocGrid = () => {
const { isDesktop, screenWidth } = useResponsiveStore();
const flexLeft = useMemo(() => {
if (!isDesktop) {
return 1;
} else if (screenWidth <= 1100) {
return 6;
} else if (screenWidth < 1200) {
return 8;
}
return 8;
}, [isDesktop, screenWidth]);
const flexRight = useMemo(() => {
if (!isDesktop) {
return undefined;
} else if (screenWidth <= 1200) {
return 5;
}
return 4;
}, [isDesktop, screenWidth]);
return { flexLeft, flexRight };
};

View File

@@ -1,2 +1 @@
export * from './useDate';
export * from './useClipboard';

View File

@@ -1,34 +0,0 @@
import { VariantType, useToastProvider } from '@openfun/cunningham-react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export const useClipboard = () => {
const { toast } = useToastProvider();
const { t } = useTranslation();
return useCallback(
(text: string, successMessage?: string, errorMessage?: string) => {
navigator.clipboard
.writeText(text)
.then(() => {
toast(
successMessage ?? t('Copied to clipboard'),
VariantType.SUCCESS,
{
duration: 3000,
},
);
})
.catch(() => {
toast(
errorMessage ?? t('Failed to copy to clipboard'),
VariantType.ERROR,
{
duration: 3000,
},
);
});
},
[t, toast],
);
};

View File

@@ -9,6 +9,7 @@
"Accessibility statement": "Erklärung zur Barrierefreiheit",
"Add": "Hinzufügen",
"Address:": "Anschrift:",
"Administrator": "Administrator",
"All docs": "Alle Dokumente",
"Anonymous": "Gast",
"Anyone with the link can edit the document": "Jeder mit dem Link kann das Dokument bearbeiten",
@@ -26,7 +27,6 @@
"Content modal to delete document": "Inhalts-Modal zum Löschen des Dokuments",
"Content modal to export the document": "Inhalte zum Exportieren des Dokuments",
"Convert Markdown": "Markdown konvertieren",
"Cookies placed": "Cookies gesetzt",
"Copied to clipboard": "In die Zwischenablage kopiert",
"Copy as {{format}}": "Als {{format}} kopieren",
"Copy link": "Link kopieren",
@@ -35,33 +35,33 @@
"Delete a doc": "Dokument löschen",
"Delete document": "Dokument löschen",
"Doc visibility card": "Dokumenten-Sichtbarkeitskarte",
"Docs": "Docs",
"Docs Logo": "Docs Logo",
"Docs: Your new companion to collaborate on documents efficiently, intuitively, and securely.": "Pages: Ihr neuer Begleiter für eine effiziente, intuitive und sichere Zusammenarbeit bei Dokumenten.",
"Document owner": "Besitzer des Dokuments",
"Document title updated successfully": "Titel des Dokuments erfolgreich aktualisiert",
"Download": "Herunterladen",
"E-mail:": "E-Mail:",
"Edition": "Bearbeiten",
"Editor": "Editor",
"Editor unavailable": "Editor nicht verfügbar",
"Error during delete invitation": "Fehler beim Löschen der Einladung",
"Error during invitation update": "Fehler beim Aktualisieren der Einladung",
"Error during update invitation": "Fehler beim Aktualisieren der Einladung",
"Error while deleting invitation": "Fehler beim Löschen der Einladung",
"Established on December 20, 2023.": "Gegründet am 20. Dezember 2023.",
"Export": "Exportieren",
"Failed to add the member in the document.": "Fehler beim Hinzufügen des Mitglieds zum Dokument.",
"Failed to copy link": "Link konnte nicht kopiert werden",
"Failed to copy to clipboard": "Fehler beim Kopieren in die Zwischenablage",
"Failed to create the invitation for {{email}}.": "Fehler beim Erstellen der Einladung für {{email}}.",
"Format": "Format",
"History": "Versionsverlauf",
"If a member is editing, his works can be lost.": "Wenn ein Mitglied editiert, können seine Änderungen verloren gehen.",
"If you are unable to access a content or a service, you can contact the person responsible for https://lasuite.numerique.gouv.fr to be directed to an accessible alternative or to obtain the content in another form.": "Wenn Sie keinen Zugriff auf Inhalte oder einen Service haben, können Sie sich an die für https://lasuite.numerique.gouv.fr verantwortliche Person wenden, um eine zugängliche Alternative zu erhalten oder den Inhalt in einer anderen Form zu erhalten.",
"Illustration:": "Abbildung:",
"Improvement and contact": "Verbesserungen und Kontakt",
"Invite": "Einladen",
"It is the card information about the document.": "Es handelt sich um die Karteninformationen zum Dokument.",
"It is the document title": "Es ist der Titel des Dokuments",
"It seems that the page you are looking for does not exist or cannot be displayed correctly.": "Es scheint, dass die von Ihnen gesuchte Seite nicht existiert oder nicht korrekt angezeigt werden kann.",
"It's true, you didn't have to click on a block that covers half the page to say you agree to the placement of cookies — even if you don't know what it means!": "Es stimmt, Sie mussten nicht auf einen Block klicken, der die halbe Seite bedeckt, um zu sagen, dass Sie der Platzierung von Cookies zustimmen - auch wenn Sie nicht wissen, was es bedeutet!",
"Language": "Sprache",
"Last update: {{update}}": "Zuletzt aktualisiert: {{update}}",
"Legal Notice": "Impressum",
@@ -75,21 +75,22 @@
"Logout": "Abmelden",
"Modal confirmation to restore the version": "Modale Bestätigung um die Version wiederherzustellen",
"More docs": "Weitere Dokumente",
"More info?": "Mehr Informationen",
"My docs": "Meine Dokumente",
"Name": "Name",
"New doc": "Neues Dokument",
"No active search": "Keine aktive Suche",
"No document found": "Kein Dokument gefunden",
"No documents found": "Keine Dokumente gefunden",
"No editor found": "Kein Editor gefunden",
"No versions": "Keine Versionen",
"Nothing exceptional, no special privileges related to a .gouv.fr.": "Nichts Außergewöhnliches, keine besonderen Privilegien im Zusammenhang mit .gouv.fr.",
"OK": "OK",
"Offline ?!": "Offline?!",
"Only invited people can access": "Nur eingeladene Personen haben Zugriff",
"Open the document options": "Öffnen Sie die Dokumentoptionen",
"Open the header menu": "Öffne das Kopfzeilen-Menü",
"Ouch !": "Autsch!",
"Owner": "Besitzer",
"PDF": "PDF",
"Pending invitations": "Ausstehende Einladungen",
"Personal data and cookies": "Personenbezogene Daten und Cookies",
"Pin": "Anheften",
@@ -97,12 +98,9 @@
"Private": "Privat",
"Public": "Öffentlich",
"Public document": "Öffentliches Dokument",
"Publication Director": "Verantwortlicher Herausgeber",
"Publisher": "Herausgeber",
"Quick search input": "Schnellsuche-Eingabe",
"Reader": "Leser",
"Reading": "Lesen",
"Remedies": "Rechtsbehelfe",
"Remove": "Löschen",
"Rename": "Umbenennen",
"Rephrase": "Umformulieren",
@@ -112,7 +110,6 @@
"Search user result": "Suchergebnis",
"Select a document": "Dokument auswählen",
"Select a version on the right to restore": "Wählen Sie rechts eine Version zum Wiederherstellen aus",
"Send a letter by post (free of charge, no stamp needed):": "Senden Sie einen Brief per Post (kostenlos, kein Porto erforderlich):",
"Share": "Teilen",
"Share modal": "Teilen-Modal",
"Share the document": "Dokument teilen",
@@ -121,18 +118,13 @@
"Share with {{count}} users_other": "Teilen mit {{count}} Benutzern",
"Shared with me": "Mit mir geteilt",
"Something bad happens, please retry.": "Etwas ist schiefgelaufen, bitte versuchen Sie es erneut.",
"Stéphanie Schaer: Interministerial Digital Director (DINUM).": "Stéphanie Schaer: Interministerielle Digitaldirektorin (DINUM).",
"Summarize": "Zusammenfassen",
"Summary": "Zusammenfassung",
"Template": "Vorlage",
"The document has been deleted.": "Das Dokument wurde gelöscht.",
"The document visibility has been updated.": "Die Sichtbarkeit des Dokuments wurde aktualisiert.",
"The team in charge of the digital workspace \"La Suite numérique\" can be contacted directly at": "Das Team, das für den digitalen Arbeitsbereich \"La Suite numérique\" zuständig ist, kann direkt kontaktiert werden unter",
"This accessibility statement applies to the site hosted on": "Diese Erklärung zur Barrierefreiheit gilt für die gehostete Seite",
"This allows us to measure the number of visits and understand which pages are the most viewed.": "Dies ermöglicht es uns, die Anzahl der Besuche zu messen und zu verstehen, welche Seiten am häufigsten angesehen werden.",
"This procedure should be used in the following case:": "Dieses Verfahren sollte in folgendem Fall verwendet werden:",
"This site places a small text file (a \"cookie\") on your computer when you visit it.": "Diese Website platziert beim Besuch auf Ihrem Computer eine kleine Textdatei (ein \"Cookie\").",
"This will protect your privacy, but will also prevent the owner from learning from your actions and creating a better experience for you and other users.": "Dies schützt Ihre Privatsphäre, verhindert jedoch auch, dass der Eigentümer aus Ihren Aktionen lernt und eine bessere Erfahrung für Sie und andere Benutzer schafft.",
"This site does not display a cookie consent banner, why?": "",
"Too many requests. Please wait 60 seconds.": "Zu viele Anfragen. Bitte warten Sie 60 Sekunden.",
"Type a name or email": "Geben Sie einen Namen oder eine E-Mail-Adresse ein",
"Type the name of a document": "Geben Sie den Namen eines Dokuments ein",
@@ -147,15 +139,12 @@
"Visibility": "Sichtbarkeit",
"Visibility mode": "Sichtbarkeitseinstellungen",
"Warning": "Warnung",
"We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.": "Wir halten uns einfach an das Gesetz, das besagt, dass bestimmte Publikumsmessungstools, die ordnungsgemäß konfiguriert sind, um die Privatsphäre zu respektieren, von einer vorherigen Genehmigung befreit sind.",
"We try to respond within 2 working days.": "Wir versuchen, innerhalb von 2 Arbeitstagen zu antworten.",
"Word / Open Office": "Word / Open Office",
"You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.": "Sie sind der einzige Besitzer dieser Gruppe. Machen Sie ein anderes Mitglied zum Gruppenbesitzer, bevor Sie Ihre eigene Rolle ändern oder aus Ihrem Dokument entfernen können.",
"You can oppose the tracking of your browsing on this website.": "Sie können der Verfolgung Ihres Surfverhaltens auf dieser Website widersprechen.",
"You can:": "Sie können:",
"You cannot update the role or remove other owner.": "Sie können die Rolle nicht aktualisieren oder einen anderen Besitzer entfernen.",
"Your current document will revert to this version.": "Ihr aktuelles Dokument wird auf diese Version zurückgesetzt.",
"Your {{format}} was downloaded succesfully": "Ihr {{format}} wurde erfolgreich heruntergeladen",
"you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.": "sie haben dem Website-Manager einen Mangel an Barrierefreiheit gemeldet, der Ihnen den Zugriff auf Inhalte oder einen der Dienste des Portals verwehrt, und Sie haben keine zufriedenstellende Antwort erhalten."
"Your {{format}} was downloaded succesfully": "Ihr {{format}} wurde erfolgreich heruntergeladen"
}
},
"en": { "translation": {} },
@@ -334,6 +323,5 @@
"accessibility-not-audit": "<strong>docs.numerique.gouv.fr</strong> n'est pas en conformité avec le RGAA 4.1. Le site n'a <strong>pas encore été audité.</strong>",
"you have reported to the website manager a lack of accessibility that prevents you from accessing content or one of the services of the portal and you have not received a satisfactory response.": "vous avez signalé au responsable du site internet un défaut d'accessibilité qui vous empêche d'accéder à un contenu ou à un des services du portail et vous n'avez pas obtenu de réponse satisfaisante."
}
},
"nl": { "translation": {} }
}
}

View File

@@ -1,46 +0,0 @@
import { Router } from 'next/router';
import posthog from 'posthog-js';
import { PostHogProvider as PHProvider } from 'posthog-js/react';
import { PropsWithChildren, useEffect } from 'react';
export interface PostHogConf {
id: string;
host: string;
}
interface PostHogProviderProps {
conf?: PostHogConf;
}
export function PostHogProvider({
children,
conf,
}: PropsWithChildren<PostHogProviderProps>) {
useEffect(() => {
if (!conf?.id || !conf?.host || posthog.__loaded) {
return;
}
posthog.init(conf.id, {
api_host: conf.host,
person_profiles: 'always',
loaded: (posthog) => {
if (process.env.NODE_ENV === 'development') {
posthog.debug();
}
},
capture_pageview: false,
capture_pageleave: true,
});
const handleRouteChange = () => posthog?.capture('$pageview');
Router.events.on('routeChangeComplete', handleRouteChange);
return () => {
Router.events.off('routeChangeComplete', handleRouteChange);
};
}, [conf?.host, conf?.id]);
return <PHProvider client={posthog}>{children}</PHProvider>;
}

View File

@@ -1,2 +1 @@
export * from './Crisp';
export * from './Posthog';

View File

@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "2.0.1",
"version": "2.0.0",
"private": true,
"workspaces": {
"packages": [

View File

@@ -1,6 +1,6 @@
{
"name": "eslint-config-impress",
"version": "2.0.1",
"version": "2.0.0",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js ."

View File

@@ -1,6 +1,6 @@
{
"name": "packages-i18n",
"version": "2.0.1",
"version": "2.0.0",
"private": true,
"scripts": {
"extract-translation": "yarn extract-translation:impress",

View File

@@ -1,6 +1,6 @@
{
"name": "server-y-provider",
"version": "2.0.1",
"version": "2.0.0",
"description": "Y.js provider for docs",
"repository": "https://github.com/numerique-gouv/impress",
"license": "MIT",

View File

@@ -988,56 +988,7 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@blocknote/core@0.21.0", "@blocknote/core@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@blocknote/core/-/core-0.21.0.tgz#b54baaa3eca3b700c80c59113a837c3d4153dca2"
integrity sha512-TQAN0qRCkXpz5AwfdxjuFvIKWsU4bBI5d/e5iX7PEkhwf4PFgPHsMUl2uuGw5o/hhjzoU54YKaAjlJlWyw4goA==
dependencies:
"@emoji-mart/data" "^1.2.1"
"@tiptap/core" "^2.7.1"
"@tiptap/extension-bold" "^2.7.1"
"@tiptap/extension-code" "^2.7.1"
"@tiptap/extension-collaboration" "^2.7.1"
"@tiptap/extension-collaboration-cursor" "^2.7.1"
"@tiptap/extension-gapcursor" "^2.7.1"
"@tiptap/extension-hard-break" "^2.7.1"
"@tiptap/extension-history" "^2.7.1"
"@tiptap/extension-horizontal-rule" "^2.7.1"
"@tiptap/extension-italic" "^2.7.1"
"@tiptap/extension-link" "^2.7.1"
"@tiptap/extension-paragraph" "^2.7.1"
"@tiptap/extension-strike" "^2.7.1"
"@tiptap/extension-table-cell" "^2.7.1"
"@tiptap/extension-table-header" "^2.7.1"
"@tiptap/extension-table-row" "^2.7.1"
"@tiptap/extension-text" "^2.7.1"
"@tiptap/extension-underline" "^2.7.1"
"@tiptap/pm" "^2.7.1"
emoji-mart "^5.6.0"
hast-util-from-dom "^4.2.0"
prosemirror-dropcursor "^1.8.1"
prosemirror-highlight "^0.9.0"
prosemirror-model "^1.23.0"
prosemirror-state "^1.4.3"
prosemirror-tables "^1.6.1"
prosemirror-transform "^1.9.0"
prosemirror-view "^1.33.7"
rehype-format "^5.0.0"
rehype-parse "^8.0.4"
rehype-remark "^9.1.2"
rehype-stringify "^9.0.3"
remark-gfm "^3.0.1"
remark-parse "^10.0.1"
remark-rehype "^10.1.0"
remark-stringify "^10.0.2"
shiki "^1.22.0"
unified "^10.1.2"
uuid "^8.3.2"
y-prosemirror "1.2.13"
y-protocols "^1.0.6"
yjs "^13.6.15"
"@blocknote/core@^0.22.0":
"@blocknote/core@0.22.0", "@blocknote/core@^0.22.0":
version "0.22.0"
resolved "https://registry.yarnpkg.com/@blocknote/core/-/core-0.22.0.tgz#2f363f9677d4fa5f20299b22850f5f34a6340a55"
integrity sha512-AAEx01zK6u+b1SsZniMm/aogEMjasF4vA9ZHgFGj04G7AwK5Hjwa0Sxre58qcW+KzuvR09CQHTkwjmgVmJX/HA==
@@ -1086,31 +1037,19 @@
y-protocols "^1.0.6"
yjs "^13.6.15"
"@blocknote/mantine@0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@blocknote/mantine/-/mantine-0.21.0.tgz#b8a640f498a4884129fe33f854be8d2bb842ea41"
integrity sha512-GAxgvn/87wDyE8qdkystTkEbqE8AFO81gaMJ6df0P6ZAdfIH3sFYUf9MffVOjtq7T6NSCM9vHNnhHsC9K8m/fg==
"@blocknote/mantine@0.22.0":
version "0.22.0"
resolved "https://registry.yarnpkg.com/@blocknote/mantine/-/mantine-0.22.0.tgz#15509aaefe88c3efd73a884b9fb1e0584a6223ec"
integrity sha512-6irIKCGUpE47X8qWLx9oa5ndztSrvLEHgVRp+fdVUHMJCx0/OzijJyYTTFKw8yEI9qc01pjmwdYMZrMMZybyGw==
dependencies:
"@blocknote/core" "^0.21.0"
"@blocknote/react" "^0.21.0"
"@blocknote/core" "^0.22.0"
"@blocknote/react" "^0.22.0"
"@mantine/core" "^7.10.1"
"@mantine/hooks" "^7.10.1"
"@mantine/utils" "^6.0.21"
react-icons "^5.2.1"
"@blocknote/react@0.21.0", "@blocknote/react@^0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@blocknote/react/-/react-0.21.0.tgz#ad8907f89575e8c139d07d75bdb66ef4e33f84f9"
integrity sha512-eBKe3hihGNeO4G/qKKJ/B5uuEmWm8XMbT8SxJ2zpNTjHx5lLP45vhtjAM+HCzQqz4xYacc2NphUIdjPPH5eXrQ==
dependencies:
"@blocknote/core" "^0.21.0"
"@floating-ui/react" "^0.26.4"
"@tiptap/core" "^2.7.1"
"@tiptap/react" "^2.7.1"
lodash.merge "^4.6.2"
react-icons "^5.2.1"
"@blocknote/react@^0.22.0":
"@blocknote/react@0.22.0", "@blocknote/react@^0.22.0":
version "0.22.0"
resolved "https://registry.yarnpkg.com/@blocknote/react/-/react-0.22.0.tgz#a17167a26b70ef421218ae3e49d15cca751291f0"
integrity sha512-Y6Oj99iOKnlh2FE/lgy8kO5PziPnA8MyEJyjCH9Jbvlc9t493L9EFmLK8iKBZek7sh0TOzhXGBOA6lIpk02X6A==
@@ -1136,42 +1075,6 @@
y-protocols "^1.0.6"
yjs "^13.6.15"
"@blocknote/xl-docx-exporter@0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-docx-exporter/-/xl-docx-exporter-0.21.0.tgz#f7f50bac0e3110d8851b3969f704663542d28f59"
integrity sha512-uJiyfAXlONFu6ty6KbH7zrQUYxlZS2nya3fhCAWFTB3zEz6HiwiVE4HeC5jqDgMAw/i2zzluEGOQv3O5qW4KnQ==
dependencies:
"@blocknote/core" "^0.21.0"
buffer "^6.0.3"
docx "^9.0.2"
sharp "^0.33.5"
"@blocknote/xl-multi-column@0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-multi-column/-/xl-multi-column-0.21.0.tgz#913bf07d6fb2c5445a6e4a375646997035e351e2"
integrity sha512-nWRZCP14pcbbA4F274kBL4UXI6RNmxZRKynsqACBPC/B3bns8GDh0GWMEPz/KN/6kuOkm/bYHHCkglDlEIEF1Q==
dependencies:
"@blocknote/core" "^0.21.0"
"@blocknote/react" "^0.21.0"
"@tiptap/core" "^2.7.1"
prosemirror-model "^1.23.0"
prosemirror-state "^1.4.3"
prosemirror-tables "^1.3.7"
prosemirror-transform "^1.9.0"
prosemirror-view "^1.33.7"
react-icons "^5.2.1"
"@blocknote/xl-pdf-exporter@0.21.0":
version "0.21.0"
resolved "https://registry.yarnpkg.com/@blocknote/xl-pdf-exporter/-/xl-pdf-exporter-0.21.0.tgz#6e05b20ba331cdd17162f7d42678bdfc54e9914f"
integrity sha512-OAw8nIuDDBXeMSdMkjBIYvsu6i3qhxo5OwqzcQzAV8wcHXvPnkcJiBE7RNTcawPbCdgBybx3i/oW9RT+dWcOrA==
dependencies:
"@blocknote/core" "^0.21.0"
"@blocknote/react" "^0.21.0"
"@react-pdf/renderer" "^4.0.0"
buffer "^6.0.3"
docx "^9.0.2"
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
@@ -3378,145 +3281,6 @@
"@react-types/shared" "^3.26.0"
"@swc/helpers" "^0.5.0"
"@react-pdf/fns@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/fns/-/fns-3.0.0.tgz#2e0137d48b14c531b2f6a9214cb36ea2a7aea3ba"
integrity sha512-ICbIWR93PE6+xf2Xd/fXYO1dAuiOAJaszEuGGv3wp5lLSeeelDXlEYLh6R05okxh28YqMzc0Qd85x6n6MtaLUQ==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/font@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/font/-/font-3.0.1.tgz#ea831f272e3b1418ffd2d9f70c835236e6564354"
integrity sha512-s+0xrQabGoYDDZwVpz8PXp1ylwabqiMhzfyetvxBqjDuQ15PuoSkmUkKUOkfDzauuAqs0MLMvt+Pcv+NioLfzw==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/types" "^2.7.0"
fontkit "^2.0.2"
is-url "^1.2.4"
"@react-pdf/image@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/image/-/image-3.0.1.tgz#5c08a7ddf5d07c53ea3377348e7bb07d17efe7c2"
integrity sha512-Hd5F1LzjuzG4bL/ytaOYxwN/5ip8oFBYDHdpccOfYY87J/Ca7AL31SsuneLk9DtnwNM1BSAKXtBo/WDFY3r57A==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/png-js" "^3.0.0"
jay-peg "^1.1.0"
"@react-pdf/layout@^4.2.0":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@react-pdf/layout/-/layout-4.2.0.tgz#e2489992d4b6409a6cecf62b1b9391143ad30495"
integrity sha512-/0jMhDKwZH0lQs3umNsOduaPtkK0IUpaBRUEv4udHVD9lB2VzYoSNeYsCu+MJMPJyByXj70OSWV7IMjWTCKwWw==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.0.0"
"@react-pdf/image" "^3.0.1"
"@react-pdf/pdfkit" "^4.0.0"
"@react-pdf/primitives" "^4.0.0"
"@react-pdf/stylesheet" "^5.2.0"
"@react-pdf/textkit" "^5.0.1"
"@react-pdf/types" "^2.7.0"
emoji-regex "^10.3.0"
queue "^6.0.1"
yoga-layout "^3.1.0"
"@react-pdf/pdfkit@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/pdfkit/-/pdfkit-4.0.0.tgz#8dbbfc8e546ebd0b76e88bd525fd6bec43c19df4"
integrity sha512-HaaAoBpoRGJ6c1ZOANNQZ3q6Ehmagqa8n40x+OZ5s9HcmUviZ34SCm+QBa42s1o4299M+Lgw3UoqpW7sHv3/Hg==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/png-js" "^3.0.0"
browserify-zlib "^0.2.0"
crypto-js "^4.2.0"
fontkit "^2.0.2"
jay-peg "^1.1.0"
vite-compatible-readable-stream "^3.6.1"
"@react-pdf/png-js@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/png-js/-/png-js-3.0.0.tgz#c0b7dc7c77e36f0830e9b7bccca7ddd64ada1c5e"
integrity sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==
dependencies:
browserify-zlib "^0.2.0"
"@react-pdf/primitives@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@react-pdf/primitives/-/primitives-4.0.0.tgz#0a710664923547c315386e82a643e58008612844"
integrity sha512-yp4E0rDL03NaUp/CnDBz3HQNfH2Mzdlgku57yhTMGNzetwB0NJusXcjYg5XsTGIXnR7Tv80JKI4O4ajj+oaLeQ==
"@react-pdf/reconciler@^1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@react-pdf/reconciler/-/reconciler-1.1.3.tgz#6fbd0f601fcb4f7ff1ff7c4dcc4df6afbccd9129"
integrity sha512-4vqY0klmUH32kTFvuqdAszkOpwfZYKMLO4VpJ5xZWTsoUOLQSyhC2QM2QCj9eaxpB2Nd5Kl9uW+KfyutvZnMzQ==
dependencies:
object-assign "^4.1.1"
scheduler "0.25.0-rc-603e6108-20241029"
"@react-pdf/render@^4.0.2":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@react-pdf/render/-/render-4.0.2.tgz#66284862329a926589f62170b8c3adff55d76157"
integrity sha512-5QJB9sS0uU5ALTLxrtT073VT1imZhrzuOun+7kvo0nykeAr9I4lv0Shmy8rS4QhpmXn8ASmhd17WjCVm4DcJlw==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.0.0"
"@react-pdf/primitives" "^4.0.0"
"@react-pdf/textkit" "^5.0.1"
"@react-pdf/types" "^2.7.0"
abs-svg-path "^0.1.1"
color-string "^1.9.1"
normalize-svg-path "^1.1.0"
parse-svg-path "^0.1.2"
svg-arc-to-cubic-bezier "^3.2.0"
"@react-pdf/renderer@4.1.6", "@react-pdf/renderer@^4.0.0":
version "4.1.6"
resolved "https://registry.yarnpkg.com/@react-pdf/renderer/-/renderer-4.1.6.tgz#257d4871192edb4d1716bc6399b5a7cf73ee3db3"
integrity sha512-hfQ0PsuVqfoYxkYgmkj+HFkylbB1QTpXY1rnlgnzJlrlSoNXjzPrCa/ty8jcHOwYA2lNoazIAoDatBIsc8K5pw==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/font" "^3.0.1"
"@react-pdf/layout" "^4.2.0"
"@react-pdf/pdfkit" "^4.0.0"
"@react-pdf/primitives" "^4.0.0"
"@react-pdf/reconciler" "^1.1.3"
"@react-pdf/render" "^4.0.2"
"@react-pdf/types" "^2.7.0"
events "^3.3.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
queue "^6.0.1"
"@react-pdf/stylesheet@^5.2.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@react-pdf/stylesheet/-/stylesheet-5.2.0.tgz#01ed0462c363842babf5e21de913bbfe3fff23c6"
integrity sha512-ST19VumM9iRG0z8EjDJnyQCG+NhPFtYUCAh5B8HY237MrsRGvMgzcwrpyyqcyuLwHHYy7S4iw8EY0mK9+Qa2XQ==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.0.0"
"@react-pdf/types" "^2.7.0"
color-string "^1.9.1"
hsl-to-hex "^1.0.0"
media-engine "^1.0.3"
postcss-value-parser "^4.1.0"
"@react-pdf/textkit@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@react-pdf/textkit/-/textkit-5.0.1.tgz#284e55ff016f46c8bf364aa1d7bcef772c913662"
integrity sha512-4GdDiPA9l+If203hkh48slvRQmcmM3ecPLFTpXNMPrep/3retgvxUEXKMxI+xKclpw8tMzK/W6Z4hN9DgnxWMg==
dependencies:
"@babel/runtime" "^7.20.13"
"@react-pdf/fns" "3.0.0"
bidi-js "^1.0.2"
hyphen "^1.6.4"
unicode-properties "^1.4.1"
"@react-pdf/types@^2.7.0":
version "2.7.0"
resolved "https://registry.yarnpkg.com/@react-pdf/types/-/types-2.7.0.tgz#56a0232ce420c313fe67cef193cbf57870a01477"
integrity sha512-7KrPPCpgRPKR+g+T127PE4bpw9Q84ZiY07EYRwXKVtTEVW9wJ5BZiF9smT9IvH19s+MQaDLmYRgjESsnqlyH0Q==
"@react-stately/calendar@3.5.4":
version "3.5.4"
resolved "https://registry.yarnpkg.com/@react-stately/calendar/-/calendar-3.5.4.tgz#847b2a2e5cf13a81b3344f1ef4e9a0d10138191e"
@@ -4553,7 +4317,7 @@
resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
"@swc/helpers@0.5.15", "@swc/helpers@^0.5.0", "@swc/helpers@^0.5.12":
"@swc/helpers@0.5.15", "@swc/helpers@^0.5.0":
version "0.5.15"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==
@@ -5093,7 +4857,7 @@
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@22.10.3", "@types/node@^22.7.5":
"@types/node@*", "@types/node@22.10.3":
version "22.10.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.3.tgz#cdc2a89bf6e5d5e593fad08e83f74d7348d5dd10"
integrity sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==
@@ -5522,11 +5286,6 @@ abab@^2.0.6:
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
abs-svg-path@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/abs-svg-path/-/abs-svg-path-0.1.1.tgz#df601c8e8d2ba10d4a76d625e236a9a39c2723bf"
integrity sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==
accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
@@ -5955,18 +5714,11 @@ bare-events@^2.2.0:
resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.0.tgz#305b511e262ffd8b9d5616b056464f8e1b3329cc"
integrity sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==
base64-js@^1.1.2, base64-js@^1.3.0, base64-js@^1.3.1:
base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
bidi-js@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2"
integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==
dependencies:
require-from-string "^2.0.2"
binary-extensions@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
@@ -6063,20 +5815,6 @@ broccoli-plugin@^4.0.7:
rimraf "^3.0.2"
symlink-or-copy "^1.3.1"
brotli@^1.3.2:
version "1.3.3"
resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48"
integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==
dependencies:
base64-js "^1.1.2"
browserify-zlib@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f"
integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==
dependencies:
pako "~1.0.5"
browserslist@^4.24.0, browserslist@^4.24.2:
version "4.24.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.3.tgz#5fc2725ca8fb3c1432e13dac278c7cc103e026d2"
@@ -6348,7 +6086,7 @@ color-name@^1.0.0, color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.9.0, color-string@^1.9.1:
color-string@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
@@ -6487,7 +6225,7 @@ core-js-compat@^3.38.0, core-js-compat@^3.38.1:
dependencies:
browserslist "^4.24.2"
core-js@^3.0.0, core-js@^3.38.1:
core-js@^3.0.0:
version "3.39.0"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.39.0.tgz#57f7647f4d2d030c32a72ea23a0555b2eaa30f83"
integrity sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==
@@ -6580,11 +6318,6 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
crypto-random-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
@@ -6860,11 +6593,6 @@ dezalgo@^1.0.4:
asap "^2.0.0"
wrappy "1"
dfa@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657"
integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==
diff-sequences@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
@@ -6901,30 +6629,6 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
docx@9.1.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/docx/-/docx-9.1.0.tgz#e089b86048df0fb777f0a957bc45c7a591b181f8"
integrity sha512-XOtseSTRrkKN/sV5jNBqyLazyhNpWfaUhpuKc22cs+5DavNjRQvchnohb0g0S+x/96/D06U/i0/U/Gc4E5kwuQ==
dependencies:
"@types/node" "^22.7.5"
hash.js "^1.1.7"
jszip "^3.10.1"
nanoid "^5.0.4"
xml "^1.0.1"
xml-js "^1.6.8"
docx@^9.0.2:
version "9.1.1"
resolved "https://registry.yarnpkg.com/docx/-/docx-9.1.1.tgz#0c72d8000e9bb5bb380c5b48656a3277eb53322d"
integrity sha512-jz941pdz4+gMljZ1pV+95FwuWEouKi4u1Elhv3ptqeytGOSyX+u131hRYg4wgqLU+x2CbGsz9eTYgo2uMMz65Q==
dependencies:
"@types/node" "^22.7.5"
hash.js "^1.1.7"
jszip "^3.10.1"
nanoid "^5.0.4"
xml "^1.0.1"
xml-js "^1.6.8"
dom-accessibility-api@^0.5.9:
version "0.5.16"
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453"
@@ -7045,11 +6749,6 @@ emoji-regex-xs@^1.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724"
integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
emoji-regex@^10.3.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4"
integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
@@ -7589,7 +7288,7 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
events@^3.2.0, events@^3.3.0:
events@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@@ -7776,11 +7475,6 @@ fetch-mock@9.11.0:
querystring "^0.2.0"
whatwg-url "^6.5.0"
fflate@^0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
figlet@1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.7.0.tgz#46903a04603fd19c3e380358418bb2703587a72e"
@@ -7870,21 +7564,6 @@ flatted@^3.2.9, flatted@^3.3.1:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27"
integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==
fontkit@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/fontkit/-/fontkit-2.0.4.tgz#4765d664c68b49b5d6feb6bd1051ee49d8ec5ab0"
integrity sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==
dependencies:
"@swc/helpers" "^0.5.12"
brotli "^1.3.2"
clone "^2.1.2"
dfa "^1.2.0"
fast-deep-equal "^3.1.3"
restructure "^3.0.0"
tiny-inflate "^1.0.3"
unicode-properties "^1.4.0"
unicode-trie "^2.0.0"
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
@@ -8261,14 +7940,6 @@ has-tostringtag@^1.0.0, has-tostringtag@^1.0.2:
dependencies:
has-symbols "^1.0.3"
hash.js@^1.1.7:
version "1.1.7"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
dependencies:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
hasown@^2.0.0, hasown@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
@@ -8552,18 +8223,6 @@ hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
dependencies:
react-is "^16.7.0"
hsl-to-hex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz#c58c826dc6d2f1e0a5ff1da5a7ecbf03faac1352"
integrity sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==
dependencies:
hsl-to-rgb-for-reals "^1.1.0"
hsl-to-rgb-for-reals@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz#e1eb23f6b78016e3722431df68197e6dcdc016d9"
integrity sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==
html-encoding-sniffer@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
@@ -8669,11 +8328,6 @@ human-signals@^2.1.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
hyphen@^1.6.4:
version "1.10.6"
resolved "https://registry.yarnpkg.com/hyphen/-/hyphen-1.10.6.tgz#0e779d280e696102b97d7e42f5ca5de2cc97e274"
integrity sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==
i18next-browser-languagedetector@8.0.2:
version "8.0.2"
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz#037ca25c26877cad778f060a9e177054d9f8eaa3"
@@ -8755,11 +8409,6 @@ ignore@^6.0.2:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-6.0.2.tgz#77cccb72a55796af1b6d2f9eb14fa326d24f4283"
integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -9084,11 +8733,6 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15:
dependencies:
which-typed-array "^1.1.16"
is-url@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52"
integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==
is-valid-glob@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
@@ -9209,13 +8853,6 @@ jake@^10.8.5:
filelist "^1.0.4"
minimatch "^3.1.2"
jay-peg@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/jay-peg/-/jay-peg-1.1.1.tgz#fdf410b89fa7a295bf74424ffe4c9083dbe7c363"
integrity sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==
dependencies:
restructure "^3.0.0"
jest-changed-files@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a"
@@ -9617,6 +9254,33 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
jsdom@25.0.1, jsdom@^25.0.1:
version "25.0.1"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef"
integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==
dependencies:
cssstyle "^4.1.0"
data-urls "^5.0.0"
decimal.js "^10.4.3"
form-data "^4.0.0"
html-encoding-sniffer "^4.0.0"
http-proxy-agent "^7.0.2"
https-proxy-agent "^7.0.5"
is-potential-custom-element-name "^1.0.1"
nwsapi "^2.2.12"
parse5 "^7.1.2"
rrweb-cssom "^0.7.1"
saxes "^6.0.0"
symbol-tree "^3.2.4"
tough-cookie "^5.0.0"
w3c-xmlserializer "^5.0.0"
webidl-conversions "^7.0.0"
whatwg-encoding "^3.1.1"
whatwg-mimetype "^4.0.0"
whatwg-url "^14.0.0"
ws "^8.18.0"
xml-name-validator "^5.0.0"
jsdom@^20.0.0:
version "20.0.3"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db"
@@ -9649,33 +9313,6 @@ jsdom@^20.0.0:
ws "^8.11.0"
xml-name-validator "^4.0.0"
jsdom@^25.0.1:
version "25.0.1"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef"
integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==
dependencies:
cssstyle "^4.1.0"
data-urls "^5.0.0"
decimal.js "^10.4.3"
form-data "^4.0.0"
html-encoding-sniffer "^4.0.0"
http-proxy-agent "^7.0.2"
https-proxy-agent "^7.0.5"
is-potential-custom-element-name "^1.0.1"
nwsapi "^2.2.12"
parse5 "^7.1.2"
rrweb-cssom "^0.7.1"
saxes "^6.0.0"
symbol-tree "^3.2.4"
tough-cookie "^5.0.0"
w3c-xmlserializer "^5.0.0"
webidl-conversions "^7.0.0"
whatwg-encoding "^3.1.1"
whatwg-mimetype "^4.0.0"
whatwg-url "^14.0.0"
ws "^8.18.0"
xml-name-validator "^5.0.0"
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
@@ -9759,16 +9396,6 @@ jsonpointer@^5.0.0:
object.assign "^4.1.4"
object.values "^1.1.6"
jszip@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
dependencies:
lie "~3.3.0"
pako "~1.0.2"
readable-stream "~2.3.6"
setimmediate "^1.0.5"
keyv@^4.5.3, keyv@^4.5.4:
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -9833,13 +9460,6 @@ lib0@^0.2.42, lib0@^0.2.47, lib0@^0.2.85, lib0@^0.2.87, lib0@^0.2.98:
dependencies:
isomorphic.js "^0.2.4"
lie@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
dependencies:
immediate "~3.0.5"
lilconfig@^3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.3.tgz#a1bcfd6257f9585bf5ae14ceeebb7b559025e4c4"
@@ -10205,11 +9825,6 @@ mdurl@^2.0.0:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-2.0.0.tgz#80676ec0433025dd3e17ee983d0fe8de5a2237e0"
integrity sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==
media-engine@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/media-engine/-/media-engine-1.0.3.tgz#be3188f6cd243ea2a40804a35de5a5b032f58dad"
integrity sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -10590,11 +10205,6 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -10673,11 +10283,6 @@ nanoid@^3.3.6, nanoid@^3.3.7:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
nanoid@^5.0.4:
version "5.0.9"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.9.tgz#977dcbaac055430ce7b1e19cf0130cea91a20e50"
integrity sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -10774,13 +10379,6 @@ normalize-path@3.0.0, normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-svg-path@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz#0e614eca23c39f0cffe821d6be6cd17e569a766c"
integrity sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==
dependencies:
svg-arc-to-cubic-bezier "^3.0.0"
now-and-later@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-3.0.0.tgz#cdc045dc5b894b35793cf276cc3206077bb7302d"
@@ -10966,16 +10564,6 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
pako@^0.2.5:
version "0.2.9"
resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==
pako@~1.0.2, pako@~1.0.5:
version "1.0.11"
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -10993,11 +10581,6 @@ parse-json@^5.0.0, parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parse-svg-path@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/parse-svg-path/-/parse-svg-path-0.1.2.tgz#7a7ec0d1eb06fa5325c7d3e009b859a09b5d49eb"
integrity sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==
parse5-htmlparser2-tree-adapter@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz#b5a806548ed893a43e24ccb42fbb78069311e81b"
@@ -11196,7 +10779,7 @@ postcss-selector-parser@^7.0.0:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
postcss-value-parser@^4.0.2, postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@@ -11277,21 +10860,6 @@ postgres-range@^1.1.1:
resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863"
integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==
posthog-js@1.204.0:
version "1.204.0"
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.204.0.tgz#73843af471fcc484ca1e8e1bcc927887cf81b4ba"
integrity sha512-wVt948wKPPztCZ3OeDq8y0dtaPbhbY8vFuEVBUNHOn7PohbTXr7HZ4CNhH8fXgFkx5COEzz/20wWJmEsSU5oCA==
dependencies:
core-js "^3.38.1"
fflate "^0.4.8"
preact "^10.19.3"
web-vitals "^4.2.0"
preact@^10.19.3:
version "10.25.4"
resolved "https://registry.yarnpkg.com/preact/-/preact-10.25.4.tgz#c1d00bee9d7b9dcd06a2311d9951973b506ae8ac"
integrity sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -11493,7 +11061,7 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.4.3:
prosemirror-transform "^1.0.0"
prosemirror-view "^1.27.0"
prosemirror-tables@^1.3.7, prosemirror-tables@^1.6.1:
prosemirror-tables@^1.6.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/prosemirror-tables/-/prosemirror-tables-1.6.2.tgz#cec9e9ac6ecf81d67147c19ab39125d56c8351ae"
integrity sha512-97dKocVLrEVTQjZ4GBLdrrMw7Gv3no8H8yMwf5IRM9OoHrzbWpcH5jJxYgNQIRCtdIqwDctT1HdMHrGTiwp1dQ==
@@ -11512,7 +11080,7 @@ prosemirror-trailing-node@^3.0.0:
"@remirror/core-constants" "3.0.0"
escape-string-regexp "^4.0.0"
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.7.3, prosemirror-transform@^1.9.0:
prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.10.2, prosemirror-transform@^1.7.3:
version "1.10.2"
resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz#8ebac4e305b586cd96595aa028118c9191bbf052"
integrity sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==
@@ -11607,13 +11175,6 @@ queue-tick@^1.0.1:
resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142"
integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==
queue@^6.0.1:
version "6.0.2"
resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65"
integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==
dependencies:
inherits "~2.0.3"
quick-temp@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408"
@@ -12310,11 +11871,6 @@ resolve@^2.0.0-next.5:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
restructure@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/restructure/-/restructure-3.0.2.tgz#e6b2fad214f78edee21797fa8160fef50eb9b49a"
integrity sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@@ -12425,11 +11981,6 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@^1.2.4:
version "1.4.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
saxes@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
@@ -12437,11 +11988,6 @@ saxes@^6.0.0:
dependencies:
xmlchars "^2.2.0"
scheduler@0.25.0-rc-603e6108-20241029:
version "0.25.0-rc-603e6108-20241029"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz#684dd96647e104d23e0d29a37f18937daf82df19"
integrity sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
@@ -12536,11 +12082,6 @@ set-function-name@^2.0.2:
functions-have-names "^1.2.3"
has-property-descriptors "^1.0.2"
setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==
setprototypeof@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
@@ -13131,11 +12672,6 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
svg-arc-to-cubic-bezier@^3.0.0, svg-arc-to-cubic-bezier@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz#390c450035ae1c4a0104d90650304c3bc814abe6"
integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==
svg-parser@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
@@ -13270,11 +12806,6 @@ through2@^2.0.1:
readable-stream "~2.3.6"
xtend "~4.0.1"
tiny-inflate@^1.0.0, tiny-inflate@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
tippy.js@^6.3.7:
version "6.3.7"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
@@ -13603,27 +13134,11 @@ unicode-match-property-value-ecmascript@^2.1.0:
resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71"
integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==
unicode-properties@^1.4.0, unicode-properties@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f"
integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==
dependencies:
base64-js "^1.3.0"
unicode-trie "^2.0.0"
unicode-property-aliases-ecmascript@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
unicode-trie@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8"
integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==
dependencies:
pako "^0.2.5"
tiny-inflate "^1.0.0"
unified@^10.0.0, unified@^10.1.2:
version "10.1.2"
resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df"
@@ -13985,15 +13500,6 @@ vinyl@^3.0.0:
replace-ext "^2.0.0"
teex "^1.0.1"
vite-compatible-readable-stream@^3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz#27267aebbdc9893c0ddf65a421279cbb1e31d8cd"
integrity sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
void-elements@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
@@ -14055,11 +13561,6 @@ web-namespaces@^2.0.0:
resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692"
integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==
web-vitals@^4.2.0:
version "4.2.4"
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7"
integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
@@ -14468,13 +13969,6 @@ ws@^7.4.6:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
xml-js@^1.6.8:
version "1.6.11"
resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
dependencies:
sax "^1.2.4"
xml-name-validator@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
@@ -14485,11 +13979,6 @@ xml-name-validator@^5.0.0:
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673"
integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==
xml@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==
xmlchars@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
@@ -14564,11 +14053,6 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
yoga-layout@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/yoga-layout/-/yoga-layout-3.2.1.tgz#d2d1ba06f0e81c2eb650c3e5ad8b0b4adde1e843"
integrity sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==
zustand@5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.2.tgz#f7595ada55a565f1fd6464f002a91e701ee0cfca"

View File

@@ -148,13 +148,6 @@ ingressAdmin:
enabled: true
host: impress.127.0.0.1.nip.io
posthog:
ingress:
enabled: false
ingressAssets:
enabled: false
ingressMedia:
enabled: true
host: impress.127.0.0.1.nip.io

View File

@@ -93,4 +93,4 @@ releases:
environments:
dev:
values:
- version: 2.0.1
- version: 2.0.0

View File

@@ -1,5 +1,5 @@
apiVersion: v2
type: application
name: docs
version: 2.0.1-beta.8
version: 0.0.2
appVersion: latest

View File

@@ -104,11 +104,6 @@
| `backend.migrate.restartPolicy` | backend migrate job restart policy | `Never` |
| `backend.createsuperuser.command` | backend migrate command | `["/bin/sh","-c","python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD\n"]` |
| `backend.createsuperuser.restartPolicy` | backend migrate job restart policy | `Never` |
| `backend.job` | job dedicated to run a random management command, for example after a deployment | |
| `backend.job.name` | The name to use to describe this job | `""` |
| `backend.job.command` | The management command to execute | `[]` |
| `backend.job.restartPolicy` | The restart policy for the job. | `Never` |
| `backend.job.annotations` | Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync] | |
| `backend.probes.liveness.path` | Configure path for backend HTTP liveness probe | `/__heartbeat__` |
| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `undefined` |
| `backend.probes.liveness.initialDelaySeconds` | Configure initial delay for backend liveness probe | `10` |
@@ -181,37 +176,6 @@
| `frontend.extraVolumeMounts` | Additional volumes to mount on the frontend. | `[]` |
| `frontend.extraVolumes` | Additional volumes to mount on the frontend. | `[]` |
### posthog
| Name | Description | Value |
| -------------------------------------- | ----------------------------------------------------------- | ------------------------- |
| `posthog.ingress.enabled` | Enable or disable the ingress resource creation | `false` |
| `posthog.ingress.className` | Kubernetes ingress class name to use (e.g., nginx, traefik) | `nil` |
| `posthog.ingress.host` | Primary hostname for the ingress resource | `impress.example.com` |
| `posthog.ingress.path` | URL path prefix for the ingress routes (e.g., /) | `/` |
| `posthog.ingress.hosts` | Additional hostnames array to be included in the ingress | `[]` |
| `posthog.ingress.tls.enabled` | Enable or disable TLS/HTTPS for the ingress | `true` |
| `posthog.ingress.tls.additional` | Additional TLS configurations for extra hosts/certificates | `[]` |
| `posthog.ingress.customBackends` | Custom backend service configurations for the ingress | `[]` |
| `posthog.ingress.annotations` | Additional Kubernetes annotations to apply to the ingress | `{}` |
| `posthog.ingressAssets.enabled` | Enable or disable the ingress resource creation | `false` |
| `posthog.ingressAssets.className` | Kubernetes ingress class name to use (e.g., nginx, traefik) | `nil` |
| `posthog.ingressAssets.host` | Primary hostname for the ingress resource | `impress.example.com` |
| `posthog.ingressAssets.paths` | URL paths prefix for the ingress routes (e.g., /static) | `["/static","/array"]` |
| `posthog.ingressAssets.hosts` | Additional hostnames array to be included in the ingress | `[]` |
| `posthog.ingressAssets.tls.enabled` | Enable or disable TLS/HTTPS for the ingress | `true` |
| `posthog.ingressAssets.tls.additional` | Additional TLS configurations for extra hosts/certificates | `[]` |
| `posthog.ingressAssets.customBackends` | Custom backend service configurations for the ingress | `[]` |
| `posthog.ingressAssets.annotations` | Additional Kubernetes annotations to apply to the ingress | `{}` |
| `posthog.service.type` | Service type (e.g. ExternalName, ClusterIP, LoadBalancer) | `ExternalName` |
| `posthog.service.externalName` | External service hostname when type is ExternalName | `eu.i.posthog.com` |
| `posthog.service.port` | Port number for the service | `443` |
| `posthog.service.annotations` | Additional annotations to apply to the service | `{}` |
| `posthog.assetsService.type` | Service type (e.g. ExternalName, ClusterIP, LoadBalancer) | `ExternalName` |
| `posthog.assetsService.externalName` | External service hostname when type is ExternalName | `eu-assets.i.posthog.com` |
| `posthog.assetsService.port` | Port number for the service | `443` |
| `posthog.assetsService.annotations` | Additional annotations to apply to the service | `{}` |
### yProvider
| Name | Description | Value |

View File

@@ -148,15 +148,6 @@ Requires top level scope
{{ include "impress.fullname" . }}-frontend
{{- end }}
{{/*
Full name for the Posthog
Requires top level scope
*/}}
{{- define "impress.posthog.fullname" -}}
{{ include "impress.fullname" . }}-posthog
{{- end }}
{{/*
Full name for the yProvider

View File

@@ -1,124 +0,0 @@
{{- if .Values.backend.job.command -}}
{{- $envVars := include "impress.common.env" (list . .Values.backend) -}}
{{- $fullName := include "impress.backend.fullname" . -}}
{{- $component := "backend" -}}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ $fullName }}-{{ .Values.backend.job.name | default "random" | replace "_" "-" }}
namespace: {{ .Release.Namespace | quote }}
annotations:
argocd.argoproj.io/sync-options: Replace=true,Force=true
{{- with .Values.backend.job.annotations }}
{{- toYaml . | nindent 4 }}
{{- end }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
spec:
template:
metadata:
annotations:
{{- with .Values.backend.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 8 }}
spec:
{{- if $.Values.image.credentials }}
imagePullSecrets:
- name: {{ include "impress.secret.dockerconfigjson.name" (dict "fullname" (include "impress.fullname" .) "imageCredentials" $.Values.image.credentials) }}
{{- end}}
shareProcessNamespace: {{ .Values.backend.shareProcessNamespace }}
containers:
{{- with .Values.backend.sidecars }}
{{- toYaml . | nindent 8 }}
{{- end }}
- name: {{ .Chart.Name }}
image: "{{ (.Values.backend.image | default dict).repository | default .Values.image.repository }}:{{ (.Values.backend.image | default dict).tag | default .Values.image.tag }}"
imagePullPolicy: {{ (.Values.backend.image | default dict).pullPolicy | default .Values.image.pullPolicy }}
{{- with .Values.backend.job.command }}
command:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.backend.args }}
args:
{{- toYaml . | nindent 12 }}
{{- end }}
env:
{{- if $envVars}}
{{- $envVars | indent 12 }}
{{- end }}
{{- with .Values.backend.securityContext }}
securityContext:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.backend.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
volumeMounts:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
mountPath: {{ $value.path }}
subPath: content
{{- end }}
{{- range $name, $volume := .Values.backend.persistence }}
- name: "{{ $name }}"
mountPath: "{{ $volume.mountPath }}"
{{- end }}
{{- range .Values.backend.extraVolumeMounts }}
- name: {{ .name }}
mountPath: {{ .mountPath }}
subPath: {{ .subPath | default "" }}
readOnly: {{ .readOnly }}
{{- end }}
{{- with .Values.backend.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.backend.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.backend.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
restartPolicy: {{ .Values.backend.job.restartPolicy }}
volumes:
{{- range $index, $value := .Values.mountFiles }}
- name: "files-{{ $index }}"
configMap:
name: "{{ include "impress.fullname" $ }}-files-{{ $index }}"
{{- end }}
{{- range $name, $volume := .Values.backend.persistence }}
- name: "{{ $name }}"
{{- if eq $volume.type "emptyDir" }}
emptyDir: {}
{{- else }}
persistentVolumeClaim:
claimName: "{{ $fullName }}-{{ $name }}"
{{- end }}
{{- end }}
{{- range .Values.backend.extraVolumes }}
- name: {{ .name }}
{{- if .existingClaim }}
persistentVolumeClaim:
claimName: {{ .existingClaim }}
{{- else if .hostPath }}
hostPath:
{{ toYaml .hostPath | nindent 12 }}
{{- else if .csi }}
csi:
{{- toYaml .csi | nindent 12 }}
{{- else if .configMap }}
configMap:
{{- toYaml .configMap | nindent 12 }}
{{- else if .emptyDir }}
emptyDir:
{{- toYaml .emptyDir | nindent 12 }}
{{- else }}
emptyDir: {}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -1,86 +0,0 @@
{{- if .Values.posthog.ingress.enabled -}}
{{- $fullName := include "impress.fullname" . -}}
{{- if and .Values.posthog.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.posthog.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.posthog.ingress.annotations "kubernetes.io/ingress.class" .Values.posthog.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}-posthog
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "impress.labels" . | nindent 4 }}
{{- with .Values.posthog.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.posthog.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.posthog.ingress.className }}
{{- end }}
{{- if .Values.posthog.ingress.tls.enabled }}
tls:
{{- if .Values.posthog.ingress.host }}
- secretName: {{ $fullName }}-posthog-tls
hosts:
- {{ .Values.posthog.ingress.host | quote }}
{{- end }}
{{- range .Values.posthog.ingress.tls.additional }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- if .Values.posthog.ingress.host }}
- host: {{ .Values.posthog.ingress.host | quote }}
http:
paths:
- path: {{ .Values.posthog.ingress.path }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: Prefix
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ include "impress.posthog.fullname" . }}-proxy
port:
number: {{ .Values.posthog.service.port }}
{{- else }}
serviceName: {{ include "impress.posthog.fullname" . }}-proxy
servicePort: {{ .Values.posthog.service.port }}
{{- end }}
{{- end }}
{{- range .Values.posthog.ingress.hosts }}
- host: {{ . | quote }}
http:
paths:
- path: {{ $.Values.posthog.ingress.path | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: Prefix
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ include "impress.posthog.fullname" . }}-proxy
port:
number: {{ $.Values.posthog.service.port }}
{{- else }}
serviceName: {{ include "impress.posthog.fullname" . }}-proxy
servicePort: {{ $.Values.posthog.service.port }}
{{- end }}
{{- with $.Values.posthog.service.customBackends }}
{{- toYaml . | nindent 10 }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -1,66 +0,0 @@
{{- if .Values.posthog.ingressAssets.enabled -}}
{{- $fullName := include "impress.fullname" . -}}
{{- if and .Values.posthog.ingressAssets.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.posthog.ingressAssets.annotations "kubernetes.io/ingress.class" .Values.posthog.ingressAssets.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}-posthog-assets
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "impress.labels" . | nindent 4 }}
{{- with .Values.posthog.ingressAssets.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.posthog.ingressAssets.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.posthog.ingressAssets.className }}
{{- end }}
{{- if .Values.posthog.ingressAssets.tls.enabled }}
tls:
{{- if .Values.posthog.ingressAssets.host }}
- secretName: {{ $fullName }}-posthog-tls
hosts:
- {{ .Values.posthog.ingressAssets.host | quote }}
{{- end }}
{{- range .Values.posthog.ingressAssets.tls.additional }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- if .Values.posthog.ingressAssets.host }}
- host: {{ .Values.posthog.ingressAssets.host | quote }}
http:
paths:
{{- range .Values.posthog.ingressAssets.paths }}
- path: {{ . | quote }}
{{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }}
pathType: Prefix
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ include "impress.posthog.fullname" $ }}-assets-proxy
port:
number: {{ $.Values.posthog.assetsService.port }}
{{- else }}
serviceName: {{ include "impress.posthog.fullname" $ }}-assets-proxy
servicePort: {{ $.Values.posthog.assetsService.port }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -1,24 +0,0 @@
{{- if .Values.posthog.ingressAssets.enabled -}}
{{- $envVars := include "impress.common.env" (list . .Values.posthog) -}}
{{- $fullName := include "impress.posthog.fullname" . -}}
{{- $component := "posthog" -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}-assets-proxy
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
annotations:
{{- toYaml $.Values.posthog.assetsService.annotations | nindent 4 }}
spec:
type: {{ .Values.posthog.assetsService.type }}
externalName: {{ .Values.posthog.assetsService.externalName }}
ports:
- port: {{ .Values.posthog.assetsService.port }}
targetPort: {{ .Values.posthog.assetsService.targetPort }}
protocol: TCP
name: https
selector:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }}
{{- end }}

View File

@@ -1,24 +0,0 @@
{{- if .Values.posthog.ingress.enabled -}}
{{- $envVars := include "impress.common.env" (list . .Values.posthog) -}}
{{- $fullName := include "impress.posthog.fullname" . -}}
{{- $component := "posthog" -}}
apiVersion: v1
kind: Service
metadata:
name: {{ $fullName }}-proxy
namespace: {{ .Release.Namespace | quote }}
labels:
{{- include "impress.common.labels" (list . $component) | nindent 4 }}
annotations:
{{- toYaml $.Values.posthog.service.annotations | nindent 4 }}
spec:
type: {{ .Values.posthog.service.type }}
externalName: {{ .Values.posthog.service.externalName }}
ports:
- port: {{ .Values.posthog.service.port }}
targetPort: {{ .Values.posthog.service.targetPort }}
protocol: TCP
name: https
selector:
{{- include "impress.common.selectorLabels" (list . $component) | nindent 4 }}
{{- end }}

View File

@@ -251,19 +251,6 @@ backend:
python manage.py createsuperuser --email $DJANGO_SUPERUSER_EMAIL --password $DJANGO_SUPERUSER_PASSWORD
restartPolicy: Never
## @extra backend.job job dedicated to run a random management command, for example after a deployment
## @param backend.job.name The name to use to describe this job
## @param backend.job.command The management command to execute
## @param backend.job.restartPolicy The restart policy for the job.
## @extra backend.job.annotations Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync]
## @skip backend.job.annotations.argocd.argoproj.io/hook
job:
name: ""
command: []
restartPolicy: Never
annotations:
argocd.argoproj.io/hook: PostSync
## @param backend.probes.liveness.path [nullable] Configure path for backend HTTP liveness probe
## @param backend.probes.liveness.targetPort [nullable] Configure port for backend HTTP liveness probe
## @param backend.probes.liveness.initialDelaySeconds [nullable] Configure initial delay for backend liveness probe
@@ -403,77 +390,6 @@ frontend:
## @param frontend.extraVolumes Additional volumes to mount on the frontend.
extraVolumes: []
## @section posthog
posthog:
## @param posthog.ingress.enabled Enable or disable the ingress resource creation
## @param posthog.ingress.className Kubernetes ingress class name to use (e.g., nginx, traefik)
## @param posthog.ingress.host Primary hostname for the ingress resource
## @param posthog.ingress.path URL path prefix for the ingress routes (e.g., /)
## @param posthog.ingress.hosts Additional hostnames array to be included in the ingress
## @param posthog.ingress.tls.enabled Enable or disable TLS/HTTPS for the ingress
## @param posthog.ingress.tls.additional Additional TLS configurations for extra hosts/certificates
## @param posthog.ingress.customBackends Custom backend service configurations for the ingress
## @param posthog.ingress.annotations Additional Kubernetes annotations to apply to the ingress
ingress:
enabled: false
className: null
host: impress.example.com
path: /
hosts: [ ]
tls:
enabled: true
additional: [ ]
customBackends: [ ]
annotations: {}
## @param posthog.ingressAssets.enabled Enable or disable the ingress resource creation
## @param posthog.ingressAssets.className Kubernetes ingress class name to use (e.g., nginx, traefik)
## @param posthog.ingressAssets.host Primary hostname for the ingress resource
## @param posthog.ingressAssets.paths URL paths prefix for the ingress routes (e.g., /static)
## @param posthog.ingressAssets.hosts Additional hostnames array to be included in the ingress
## @param posthog.ingressAssets.tls.enabled Enable or disable TLS/HTTPS for the ingress
## @param posthog.ingressAssets.tls.additional Additional TLS configurations for extra hosts/certificates
## @param posthog.ingressAssets.customBackends Custom backend service configurations for the ingress
## @param posthog.ingressAssets.annotations Additional Kubernetes annotations to apply to the ingress
ingressAssets:
enabled: false
className: null
host: impress.example.com
paths:
- /static
- /array
hosts: [ ]
tls:
enabled: true
additional: [ ]
customBackends: [ ]
annotations: {}
## @param posthog.service.type Service type (e.g. ExternalName, ClusterIP, LoadBalancer)
## @param posthog.service.externalName External service hostname when type is ExternalName
## @param posthog.service.port Port number for the service
## @param posthog.service.annotations Additional annotations to apply to the service
service:
type: ExternalName
externalName: eu.i.posthog.com
port: 443
annotations: {}
## @param posthog.assetsService.type Service type (e.g. ExternalName, ClusterIP, LoadBalancer)
## @param posthog.assetsService.externalName External service hostname when type is ExternalName
## @param posthog.assetsService.port Port number for the service
## @param posthog.assetsService.annotations Additional annotations to apply to the service
assetsService:
type: ExternalName
externalName: eu-assets.i.posthog.com
port: 443
annotations: {}
## @section yProvider
yProvider:

View File

@@ -1,6 +1,6 @@
{
"name": "mail_mjml",
"version": "2.0.1",
"version": "2.0.0",
"description": "An util to generate html and text django's templates from mjml templates",
"type": "module",
"dependencies": {