diff --git a/.github/workflows/impress.yml b/.github/workflows/impress.yml
index 896f07efb..385aa633c 100644
--- a/.github/workflows/impress.yml
+++ b/.github/workflows/impress.yml
@@ -206,10 +206,11 @@ jobs:
- name: Install development dependencies
run: pip install --user .[dev]
- - name: Install gettext (required to compile messages)
+ - name: Install gettext (required to compile messages) and MIME support
run: |
sudo apt-get update
- sudo apt-get install -y gettext pandoc
+ 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
- name: Generate a MO file from strings extracted from the project
run: python manage.py compilemessages
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28e07ec46..b8e55e73c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,10 @@ and this project adheres to
- 💄(frontend) add filtering to left panel #475
- ✨(frontend) new share modal ui #489
- ✨(frontend) add favorite feature #515
+- 🏷️(backend) add content-type to uploaded files #552
+- ✨(frontend) export pdf docx front side #537
+- 📝(documentation) Documentation about self-hosted installation #530
+- ✨(helm) helm versioning #530
## Changed
diff --git a/Dockerfile b/Dockerfile
index f6378f9e8..501db0d53 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -72,10 +72,11 @@ 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
diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py
index b217194d9..6066c7c29 100644
--- a/src/backend/core/api/viewsets.py
+++ b/src/backend/core/api/viewsets.py
@@ -2,6 +2,7 @@
# pylint: disable=too-many-lines
import logging
+import mimetypes
import re
import uuid
from urllib.parse import urlparse
@@ -604,8 +605,19 @@ def attachment_upload(self, request, *args, **kwargs):
extension = serializer.validated_data["expected_extension"]
key = f"{document.key_base}/{ATTACHMENTS_FOLDER:s}/{file_id!s}.{extension:s}"
+ # Determine the content type of the file
+ file = serializer.validated_data["file"]
+ content_type, _ = mimetypes.guess_type(file.name)
+
+ # Fallback if MIME type cannot be determined
+ if not content_type:
+ content_type = "application/octet-stream"
+
# Prepare metadata for storage
- extra_args = {"Metadata": {"owner": str(request.user.id)}}
+ extra_args = {
+ "Metadata": {"owner": str(request.user.id)},
+ "ContentType": content_type,
+ }
if serializer.validated_data["is_unsafe"]:
extra_args["Metadata"]["is_unsafe"] = "true"
@@ -936,40 +948,6 @@ def perform_create(self, serializer):
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,
diff --git a/src/backend/core/models.py b/src/backend/core/models.py
index b7fb8e797..2bba13e42 100644
--- a/src/backend/core/models.py
+++ b/src/backend/core/models.py
@@ -4,11 +4,8 @@
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
@@ -20,19 +17,12 @@
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 html, timezone
+from django.utils import 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
@@ -754,107 +744,6 @@ def get_abilities(self, user):
"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"""
-
-
-
-
-
-
- {template_string}
-
-
- """
-
- 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."""
diff --git a/src/backend/core/static/reference.docx b/src/backend/core/static/reference.docx
deleted file mode 100644
index 2192455df..000000000
Binary files a/src/backend/core/static/reference.docx and /dev/null differ
diff --git a/src/backend/core/tests/documents/test_api_documents_attachment_upload.py b/src/backend/core/tests/documents/test_api_documents_attachment_upload.py
index 1288f8ca6..3b0574fa0 100644
--- a/src/backend/core/tests/documents/test_api_documents_attachment_upload.py
+++ b/src/backend/core/tests/documents/test_api_documents_attachment_upload.py
@@ -64,12 +64,22 @@ def test_api_documents_attachment_upload_anonymous_success():
assert response.status_code == 201
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.png")
- match = pattern.search(response.json()["file"])
+ file_path = response.json()["file"]
+ match = pattern.search(file_path)
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",
@@ -206,6 +216,7 @@ 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):
@@ -247,16 +258,18 @@ def test_api_documents_attachment_upload_size_limit_exceeded(settings):
@pytest.mark.parametrize(
- "name,content,extension",
+ "name,content,extension,content_type",
[
- ("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"),
+ ("test.exe", b"text", "exe", "application/x-msdownload"),
+ ("test", b"text", "txt", "application/octet-stream"),
+ ("test.aaaaaa", b"test", "txt", "application/octet-stream"),
+ ("test.txt", PIXEL, "txt", "text/plain"),
+ ("test.py", b"#!/usr/bin/python", "py", "text/x-python"),
],
)
-def test_api_documents_attachment_upload_fix_extension(name, content, extension):
+def test_api_documents_attachment_upload_fix_extension(
+ name, content, extension, content_type
+):
"""
A file with no extension or a wrong extension is accepted and the extension
is corrected in storage.
@@ -287,6 +300,7 @@ def test_api_documents_attachment_upload_fix_extension(name, content, 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():
@@ -335,3 +349,4 @@ 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/x-msdownload"
diff --git a/src/backend/core/tests/templates/test_api_templates_generate_document.py b/src/backend/core/tests/templates/test_api_templates_generate_document.py
deleted file mode 100644
index 0ed8f4d39..000000000
--- a/src/backend/core/tests/templates/test_api_templates_generate_document.py
+++ /dev/null
@@ -1,208 +0,0 @@
-"""
-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": "Test body
", "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": "Test body
", "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"
- )
diff --git a/src/backend/core/tests/test_models_templates.py b/src/backend/core/tests/test_models_templates.py
index 6e7cba2c1..95f8fbdea 100644
--- a/src/backend/core/tests/test_models_templates.py
+++ b/src/backend/core/tests/test_models_templates.py
@@ -2,10 +2,6 @@
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
@@ -189,31 +185,3 @@ 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("Test body
", {})
-
- 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("Test body
", {})
- 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
diff --git a/src/backend/demo/data/template/code.txt b/src/backend/demo/data/template/code.txt
index 0ab83f606..56f6736b9 100644
--- a/src/backend/demo/data/template/code.txt
+++ b/src/backend/demo/data/template/code.txt
@@ -1,10 +1,2 @@
-
-
-
-
+
+
\ No newline at end of file
diff --git a/src/backend/demo/data/template/css.txt b/src/backend/demo/data/template/css.txt
index 79a440ab7..e69de29bb 100644
--- a/src/backend/demo/data/template/css.txt
+++ b/src/backend/demo/data/template/css.txt
@@ -1,20 +0,0 @@
-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;
-}
diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml
index 2bc8b1a60..ae6cae167 100644
--- a/src/backend/pyproject.toml
+++ b/src/backend/pyproject.toml
@@ -50,13 +50,10 @@ 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",
]
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
index 6e62d3add..eff9592a6 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
@@ -1,6 +1,7 @@
+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';
@@ -41,10 +42,8 @@ test.describe('Doc Export', () => {
).toBeVisible();
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
});
- test('it converts the doc to pdf with a template integrated', async ({
- page,
- browserName,
- }) => {
+
+ test('it exports the doc to pdf', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => {
@@ -77,10 +76,7 @@ test.describe('Doc Export', () => {
expect(pdfText).toContain('Hello World'); // This is the doc text
});
- test('it converts the doc to docx with a template integrated', async ({
- page,
- browserName,
- }) => {
+ test('it exports the doc to docx', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => {
@@ -111,120 +107,61 @@ test.describe('Doc Export', () => {
expect(download.suggestedFilename()).toBe(`${randomDoc}.docx`);
});
- test('it converts the blocknote json in correct html for the export', async ({
- page,
- browserName,
- }) => {
- test.setTimeout(60000);
-
+ /**
+ * 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);
- let body = '';
- await page.route('**/templates/*/generate-document/', async (route) => {
- const request = route.request();
- body = request.postDataJSON().body;
-
- await route.continue();
+ const fileChooserPromise = page.waitForEvent('filechooser');
+ const downloadPromise = page.waitForEvent('download', (download) => {
+ return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
await verifyDocName(page, randomDoc);
- 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();
+ await page.locator('.ProseMirror.bn-editor').click();
+ await page.locator('.ProseMirror.bn-editor').fill('Hello World');
- // 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('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');
+ await page.getByText('Resizable image with caption').click();
+ await page.getByText('Upload image').click();
- await page.keyboard.press('Enter');
- await page.keyboard.press('Backspace');
+ const fileChooser = await fileChooserPromise;
+ await fileChooser.setFiles(
+ path.join(__dirname, 'assets/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');
+ const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
+
+ 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',
+ .getByRole('button', {
+ name: 'download',
})
.click();
+
await page
- .getByPlaceholder('Enter URL')
- .fill('https://example.com/image.jpg');
- await page
- .getByRole('button', {
- name: 'Embed image',
+ .getByRole('combobox', {
+ name: 'Template',
})
.click();
- // Download
await page
- .getByRole('button', {
- name: 'download',
+ .getByRole('option', {
+ name: 'Demo Template',
})
- .click();
+ .click({
+ delay: 100,
+ });
+
+ await new Promise((resolve) => setTimeout(resolve, 1000));
await page
.getByRole('button', {
@@ -232,31 +169,13 @@ test.describe('Doc Export', () => {
})
.click();
- // Empty paragraph should be replaced by a
- expect(body.match(/ /g)?.length).toBeGreaterThanOrEqual(2);
- expect(body).toContain('style="color: orange;"');
- expect(body).toContain('custom-style="center"');
- expect(body).toContain('style="background-color: brown;"');
-
- const { JSDOM } = jsdom;
- const DOMParser = new JSDOM().window.DOMParser;
- const parser = new DOMParser();
- const html = parser.parseFromString(body, 'text/html');
-
- 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');
+ const download = await downloadPromise;
+ expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
+
+ const pdfBuffer = await cs.toBuffer(await download.createReadStream());
+ const pdfExport = await pdf(pdfBuffer);
+ const pdfText = pdfExport.text;
+
+ expect(pdfText).toContain('Hello World');
});
});
diff --git a/src/frontend/apps/e2e/package.json b/src/frontend/apps/e2e/package.json
index 290b8808e..cefbb6bc8 100644
--- a/src/frontend/apps/e2e/package.json
+++ b/src/frontend/apps/e2e/package.json
@@ -13,16 +13,15 @@
},
"devDependencies": {
"@playwright/test": "1.49.1",
+ "@types/luxon": "3.4.2",
"@types/node": "*",
"@types/pdf-parse": "1.1.4",
"eslint-config-impress": "*",
- "typescript": "*",
"luxon": "3.5.0",
- "@types/luxon": "3.4.2"
+ "typescript": "*"
},
"dependencies": {
"convert-stream": "1.0.2",
- "jsdom": "25.0.1",
"pdf-parse": "1.1.1"
}
}
diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json
index 1439798d0..8f06d7dc7 100644
--- a/src/frontend/apps/impress/package.json
+++ b/src/frontend/apps/impress/package.json
@@ -18,13 +18,17 @@
"@blocknote/core": "0.22.0",
"@blocknote/mantine": "0.22.0",
"@blocknote/react": "0.22.0",
+ "@blocknote/xl-docx-exporter": "0.22.0",
+ "@blocknote/xl-pdf-exporter": "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",
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx
index adf3ce5c2..48a7a3a6c 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx
@@ -27,7 +27,7 @@ import {
} from '@/features/docs/doc-versioning';
import { useResponsiveStore } from '@/stores';
-import { ModalPDF } from './ModalExport';
+import { ModalExport } from './ModalExport';
interface DocToolBoxProps {
doc: Doc;
@@ -43,7 +43,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
const colors = colorsTokens();
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
- const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
+ const [isModalExportOpen, setIsModalExportOpen] = useState(false);
const selectHistoryModal = useModal();
const modalShare = useModal();
@@ -66,7 +66,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
label: t('Export'),
icon: 'download',
callback: () => {
- setIsModalPDFOpen(true);
+ setIsModalExportOpen(true);
},
},
]
@@ -200,7 +200,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
}
onClick={() => {
- setIsModalPDFOpen(true);
+ setIsModalExportOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
/>
@@ -230,8 +230,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
{modalShare.isOpen && (
modalShare.close()} doc={doc} />
)}
- {isModalPDFOpen && (
- setIsModalPDFOpen(false)} doc={doc} />
+ {isModalExportOpen && (
+ setIsModalExportOpen(false)} doc={doc} />
)}
{isModalRemoveOpen && (
setIsModalRemoveOpen(false)} doc={doc} />
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx
index e08ac21dc..4e72a6eb2 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx
@@ -1,3 +1,11 @@
+import {
+ DOCXExporter,
+ docxDefaultSchemaMappings,
+} from '@blocknote/xl-docx-exporter';
+import {
+ PDFExporter,
+ pdfDefaultSchemaMappings,
+} from '@blocknote/xl-pdf-exporter';
import {
Button,
Loader,
@@ -7,91 +15,116 @@ import {
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
-import { useEffect, useMemo, useState } from 'react';
+import { pdf } from '@react-pdf/renderer';
+import { 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 { adaptBlockNoteHTML, downloadFile } from '../utils';
+import { downloadFile, exportResolveFileUrl } from '../utils';
-export enum DocDownloadFormat {
+enum DocDownloadFormat {
PDF = 'pdf',
DOCX = 'docx',
}
-interface ModalPDFProps {
+interface ModalExportProps {
onClose: () => void;
doc: Doc;
}
-export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
+export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
const { t } = useTranslation();
const { data: templates } = useTemplates({
ordering: TemplatesOrdering.BY_CREATED_ON_DESC,
});
const { toast } = useToastProvider();
const { editor } = useEditorStore();
- const {
- mutate: createExport,
- data: documentGenerated,
- isSuccess,
- isPending,
- error,
- } = useExport();
- const [templateIdSelected, setTemplateIdSelected] = useState();
+ const [templateSelected, setTemplateSelected] = useState('');
+ const [isExporting, setIsExporting] = useState(false);
const [format, setFormat] = useState(
DocDownloadFormat.PDF,
);
const templateOptions = useMemo(() => {
- if (!templates?.pages) {
- return [];
- }
-
- const templateOptions = templates.pages
+ const templateOptions = (templates?.pages || [])
.map((page) =>
page.results.map((template) => ({
label: template.title,
- value: template.id,
+ value: template.code,
})),
)
.flat();
- if (templateOptions.length) {
- setTemplateIdSelected(templateOptions[0].value);
- }
+ templateOptions.unshift({
+ label: t('Empty template'),
+ value: '',
+ });
return templateOptions;
- }, [templates?.pages]);
+ }, [t, templates?.pages]);
- useEffect(() => {
- if (!error) {
+ async function onSubmit() {
+ if (!editor) {
+ toast(t('The export failed'), VariantType.ERROR);
return;
}
- toast(error.message, VariantType.ERROR);
+ setIsExporting(true);
- 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, '-');
- downloadFile(documentGenerated, `${title}.${format}`);
+ 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}`);
toast(
t('Your {{format}} was downloaded succesfully', {
@@ -100,29 +133,9 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
VariantType.SUCCESS,
);
- onClose();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [documentGenerated, isSuccess, t]);
-
- async function onSubmit() {
- if (!templateIdSelected || !format) {
- return;
- }
+ setIsExporting(false);
- 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,
- });
+ onClose();
}
return (
@@ -138,6 +151,7 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
color="secondary"
fullWidth
onClick={() => onClose()}
+ disabled={isExporting}
>
{t('Cancel')}
@@ -146,7 +160,7 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
color="primary"
fullWidth
onClick={() => void onSubmit()}
- disabled={isPending || !templateIdSelected}
+ disabled={isExporting}
>
{t('Download')}
@@ -173,9 +187,9 @@ export const ModalPDF = ({ onClose, doc }: ModalPDFProps) => {
clearable={false}
label={t('Template')}
options={templateOptions}
- value={templateIdSelected}
+ value={templateSelected}
onChange={(options) =>
- setTemplateIdSelected(options.target.value as string)
+ setTemplateSelected(options.target.value as string)
}
/>
{
}
/>
- {isPending && (
-
+ {isExporting && (
+
)}
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
index de15b30ef..d4a6165d7 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
@@ -10,137 +10,23 @@ export function downloadFile(blob: Blob, filename: string) {
window.URL.revokeObjectURL(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"]',
- );
-
- // 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);
- }
+export const exportResolveFileUrl = async (
+ url: string,
+ resolveFileUrl: ((url: string) => Promise) | undefined,
+) => {
+ if (!url.includes(window.location.hostname) && resolveFileUrl) {
+ return resolveFileUrl(url);
+ }
+
+ try {
+ const response = await fetch(url, {
+ credentials: 'include',
});
- 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('
', ' ');
-
- // 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 response.blob();
+ } catch {
+ console.error(`Failed to fetch image: ${url}`);
+ }
- return html;
+ return url;
};
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index a073ef656..49f78a054 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -1075,6 +1075,27 @@
y-protocols "^1.0.6"
yjs "^13.6.15"
+"@blocknote/xl-docx-exporter@0.22.0":
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/@blocknote/xl-docx-exporter/-/xl-docx-exporter-0.22.0.tgz#1e90ad3f745bdefc150c87d6ce0002ac177a5f4d"
+ integrity sha512-Mq1EBorafq2gwvMo3ResA5vySUWzAbIjnZpFctxbemIMXnthTCq0Aa1jCTGoOb0EgSYxhEyOu2g5JthNEaIGvw==
+ dependencies:
+ "@blocknote/core" "^0.22.0"
+ buffer "^6.0.3"
+ docx "^9.0.2"
+ sharp "^0.33.5"
+
+"@blocknote/xl-pdf-exporter@0.22.0":
+ version "0.22.0"
+ resolved "https://registry.yarnpkg.com/@blocknote/xl-pdf-exporter/-/xl-pdf-exporter-0.22.0.tgz#c1fd730869b0bce956631886d54d5e987d0eb8fb"
+ integrity sha512-VEakGbC6cicxd25F4db2L8zIZC+abEKN4zBAmgbk/9fuS0k/vnnlm0/six7Fso3P+kwuMmcrRTJg0CWKCtQ7kw==
+ dependencies:
+ "@blocknote/core" "^0.22.0"
+ "@blocknote/react" "^0.22.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"
@@ -3281,6 +3302,145 @@
"@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"
@@ -4317,7 +4477,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.15", "@swc/helpers@^0.5.0", "@swc/helpers@^0.5.12":
version "0.5.15"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7"
integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==
@@ -4857,7 +5017,7 @@
dependencies:
"@types/node" "*"
-"@types/node@*", "@types/node@22.10.3":
+"@types/node@*", "@types/node@22.10.3", "@types/node@^22.7.5":
version "22.10.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.3.tgz#cdc2a89bf6e5d5e593fad08e83f74d7348d5dd10"
integrity sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==
@@ -5286,6 +5446,11 @@ 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"
@@ -5714,11 +5879,18 @@ 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.3.1:
+base64-js@^1.1.2, base64-js@^1.3.0, 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"
@@ -5815,6 +5987,20 @@ 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"
@@ -6086,7 +6272,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.0, color-string@^1.9.1:
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==
@@ -6318,6 +6504,11 @@ 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"
@@ -6593,6 +6784,11 @@ 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"
@@ -6629,6 +6825,18 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+docx@9.1.0, docx@^9.0.2:
+ 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"
+
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"
@@ -6749,6 +6957,11 @@ 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"
@@ -7288,7 +7501,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.2.0, events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
@@ -7564,6 +7777,21 @@ 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"
@@ -7940,6 +8168,14 @@ 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"
@@ -8223,6 +8459,18 @@ 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"
@@ -8328,6 +8576,11 @@ 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"
@@ -8409,6 +8662,11 @@ 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"
@@ -8733,6 +8991,11 @@ 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"
@@ -8853,6 +9116,13 @@ 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"
@@ -9254,33 +9524,6 @@ 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"
@@ -9313,6 +9556,33 @@ 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"
@@ -9396,6 +9666,16 @@ 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"
@@ -9460,6 +9740,13 @@ 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"
@@ -9825,6 +10112,11 @@ 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"
@@ -10205,6 +10497,11 @@ 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"
@@ -10283,6 +10580,11 @@ 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"
@@ -10379,6 +10681,13 @@ 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"
@@ -10564,6 +10873,16 @@ 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"
@@ -10581,6 +10900,11 @@ 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"
@@ -10779,7 +11103,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.2.0:
+postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, 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==
@@ -11175,6 +11499,13 @@ 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"
@@ -11871,6 +12202,11 @@ 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"
@@ -11981,6 +12317,11 @@ 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"
@@ -11988,6 +12329,11 @@ 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"
@@ -12082,6 +12428,11 @@ 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"
@@ -12672,6 +13023,11 @@ 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"
@@ -12806,6 +13162,11 @@ 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"
@@ -13134,11 +13495,27 @@ 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"
@@ -13500,6 +13877,15 @@ 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"
@@ -13969,6 +14355,13 @@ 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"
@@ -13979,6 +14372,11 @@ 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"
@@ -14053,6 +14451,11 @@ 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"