Compare commits

...

32 Commits

Author SHA1 Message Date
Anthony LC fb2850d4fb working chrome 2025-06-03 19:04:50 +02:00
Anthony LC 87597b7701 save6 2025-06-03 17:06:26 +02:00
Anthony LC ae64b7764d save5-works-almost 2025-06-03 16:48:18 +02:00
Anthony LC ed58fa228e save4 2025-06-03 16:10:23 +02:00
Anthony LC 8ceed77081 Save3 2025-06-03 16:06:23 +02:00
Anthony LC d2ba54ec3a save2 2025-06-03 15:53:22 +02:00
Anthony LC 3416e2bdf1 save1 2025-06-03 15:35:03 +02:00
renovate[bot] d952815932 ⬆️(dependencies) update python dependencies 2025-06-02 05:09:03 +00:00
renovate[bot] cde64ed80a ⬆️(dependencies) update js dependencies 2025-05-26 06:39:40 +00:00
renovate[bot] cfd88d0469 ⬆️(dependencies) update python dependencies 2025-05-26 01:55:36 +00:00
virgile-dev 5e45fec296 📝(doc) fix path to env doc on readme (#1007)
The path lead to a 404

Signed-off-by: virgile-dev <virgile.deville@beta.gouv.fr>
2025-05-25 17:01:29 +00:00
Anthony LC 393e7a06e2 🔖(minor) release 3.3.0
Added:
- (backend) add endpoint checking media status
- (backend) allow setting session cookie age via env var
- (backend) allow theme customnization using a
configuration file
- (frontend) Add a custom callout block to the editor
- 🚩(frontend) version MIT only
- (backend) integrate maleware_detection from django-lasuite
- 🏗️(frontend) Footer configurable
- 🩺(CI) add lint spell mistakes
- (frontend) create generic theme
- 🛂(frontend) block edition to not connected users
- 🚸(frontend) Let loader during upload analyze
- 🚩(frontend) feature flag on blocking edition

Changed:
- 📝(frontend) Update documentation
- (frontend) Improve tests coverage
- ⬆️(docker) upgrade backend image to python 3.13
- ⬆️(docker) upgrade node images to alpine 3.21

Fixed:
- 🐛(y-provider) increase JSON size limits for
transcription conversion

Removed:
- 🔥(back) remove footer endpoint
2025-05-23 11:41:24 +02:00
AntoLC f1af87baf8 🌐(i18n) update translated strings
Update translated files with new translations
2025-05-23 11:41:24 +02:00
Anthony LC f851ef2d85 ⬆️(dependencies) bump blocknote to 0.30.1
A bunch of fixes are in this pacth release.
Better to update now before we release to 3.3.0.
2025-05-23 11:08:07 +02:00
Anthony LC 252ab6a586 ✏️(frontend) change antivirus fail sentence
The message was not accurate when the analizer failed.
We improved the message to be more accurate.
2025-05-23 11:08:07 +02:00
Anthony LC cf2a02c8de 🚩(frontend) feature flag on blocking edition
If users were not connected to the collaboration
server, they were not be able to edit documents.

We decided to add a feature flag on this feature
as it can be quite restrictive.
We can now enable or disable this feature at runtime
thanks to the env variable
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY".
2025-05-23 11:08:07 +02:00
Anthony LC d87a2ed4eb 🔥(helm) remove useless footer config
Remove:
- FRONTEND_FOOTER_FEATURE_ENABLED
- FRONTEND_FOOTER_URL
2025-05-22 15:27:39 +02:00
Anthony LC c9d053d1c0 💄(frontend) add generic favicon
The favicons were still with the dsfr color.
We added the generic favicon in the assets folder.
The favicon can be a url loaded from the theme,
so when Drive will be running, we will be able
to store the dsfr favicons there, and remove them
from the repo.
2025-05-22 15:27:38 +02:00
Anthony LC b5f0f06ea3 💄(frontend) desaturate images system for generic theme
We want to desaturate the images system in the
generic theme to make them less colorful and more
in line with the overall theme.
We added a special class to the images
that need to be desaturated. Other property
then desaturated can be apply depending on the theme.
2025-05-22 15:27:38 +02:00
Anthony LC 36b0ff9f63 (frontend) create generic theme
By default Docs will not be on the dsfr theme but
on the generic theme. La Gaufre is part of the dsfr
theme and is removed from the generic theme.
Same for the "beta" keyword and the "proconnect"
buttons.
2025-05-22 15:27:38 +02:00
Anthony LC 7a383957a7 🔥(frontend) remove legal pages
Legal pages are not needed anymore in the application.
In the dsfr instances, the legal pages will be
displayed on a Docs pages.
We let the users of Docs managing the legal pages
on their own instances.
2025-05-22 14:07:41 +02:00
Anthony LC b5630359ee 🏗️(frontend) Footer configurable
To have different footer per instance the
content of the footer is now configurable
from the theme customization file.

See THEME_CUSTOMIZATION_FILE_PATH env var.
2025-05-22 14:07:41 +02:00
Anthony LC 310154815b ♻️(e2e) improve config testcases
Improve config testcases:
- let THEME_CUSTOMIZATION_FILE_PATH to be set to
check the default value
- add helper function overrideConfig
2025-05-22 14:07:41 +02:00
Anthony LC 2733785016 🚨(linter) add ignore pattern on no-unused-vars rule
The rule @typescript-eslint/no-unused-vars didn't
have a ignore pattern. A ignore pattern can be
usefull in some cases.
2025-05-22 14:07:41 +02:00
Manuel Raynaud 99ba414d88 🔧(back) add docs.security to logging settings
In the malware_detection callback we are using a different logger named
docs.security. We want to configure a logger in the logging settings
handling it.
2025-05-22 13:53:27 +02:00
Manuel Raynaud 41631b5b70 ⬆️(backend) upgrade django-lasuite to version 0.0.9
We need version 0.0.9 to reduce the time to have a JCOP analysis result.
2025-05-22 13:53:27 +02:00
Anthony LC 6ca654bf1a 🚸(frontend) let loader until resource ready
The backend can analyze the upload file, this take
time, so we need to show a loader
until the backend finish the analysis.
2025-05-22 13:53:27 +02:00
Manuel Raynaud 074585337b ♻️(back) return the media-check url on the attachment_upload response
We want to have the media-check url returned on the attachment-upload
response instead of the media url directly. The front will know the
endpoint to use to check the media status.
2025-05-22 13:39:44 +02:00
Manuel Raynaud f1b398e1ae (back) add endpoint checking media status
With the usage of a malware detection system, we need a way to know the
file status. The front will use it to display a loader while the analyse
is not ended.
2025-05-22 13:39:44 +02:00
Jacques ROUSSEL d1f73f18cd 🔒️(front) improve docker image security
Cyberwatch reported security issues with the frontend Docker image.
2025-05-22 11:16:57 +02:00
lebaudantoine 3f2d84bf62 🐛(y-provider) increase JSON size limits for transcription conversion
Problem:
- Default Express JSON parser limit (100kb) is insufficient for larger
 transcription files
- 2-hour audio transcriptions slightly exceed the 100kb limit, causing request
 failures

Solution:
- Implemented custom middleware to apply different JSON parser configurations
 based on route
- Applied 500kb limit specifically for transcription conversion endpoints
- Maintained default limits for all other routes to preserve security

Technical notes:
- Could not find a built-in Express solution to specify parser config per route
- Custom middleware conditionally applies the appropriate parser configuration
2025-05-21 15:31:49 +02:00
lebaudantoine 7b9c362d38 🐛(tilt) update certificate path for Python 3.13 upgrade
Fix certificate directory reference that still pointed to Python 3.12 folder
after upgrading to Python 3.13. Resolves certificate verification errors in
tilt stack caused by incorrect certificate location.
2025-05-21 12:53:41 +02:00
110 changed files with 5690 additions and 1971 deletions
+19 -8
View File
@@ -8,27 +8,37 @@ and this project adheres to
## [Unreleased]
## Added
## [3.3.0] - 2025-05-06
### Added
- ✨(backend) add endpoint checking media status #984
- ✨(backend) allow setting session cookie age via env var #977
- ✨(backend) allow theme customnization using a configuration file #948
- ✨ Add a custom callout block to the editor #892
-(frontend) Add a custom callout block to the editor #892
- 🚩(frontend) version MIT only #911
- ✨(backend) integrate maleware_detection from django-lasuite #936
- 🏗️(frontend) Footer configurable #959
- 🩺(CI) add lint spell mistakes #954
- ✨(frontend) create generic theme #792
- 🛂(frontend) block edition to not connected users #945
- 🚸(frontend) Let loader during upload analyze #984
- 🚩(frontend) feature flag on blocking edition #997
## Changed
### Changed
- 📝(frontend) Update documentation
- ✅(frontend) Improve tests coverage
- 📝(frontend) Update documentation #949
- ✅(frontend) Improve tests coverage #949
- ⬆️(docker) upgrade backend image to python 3.13 #973
- ⬆️(docker) upgrade node images to alpine 3.21
- ⬆️(docker) upgrade node images to alpine 3.21 #973
### Fixed
- 🐛(y-provider) increase JSON size limits for transcription conversion #989
### Removed
- 🔥(back) remove footer endpoint
- 🔥(back) remove footer endpoint #948
## [3.2.1] - 2025-05-06
@@ -580,7 +590,8 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.2.1...main
[unreleased]: https://github.com/numerique-gouv/impress/compare/v3.3.0...main
[v3.3.0]: https://github.com/numerique-gouv/impress/releases/v3.3.0
[v3.2.1]: https://github.com/numerique-gouv/impress/releases/v3.2.1
[v3.2.0]: https://github.com/numerique-gouv/impress/releases/v3.2.0
[v3.1.0]: https://github.com/numerique-gouv/impress/releases/v3.1.0
+1 -1
View File
@@ -57,7 +57,7 @@ Available methods: Helm chart, Nix package
In the works: Docker Compose, YunoHost
⚠️ For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under AGPL-3.0 and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/docs/env.md) for more information.
⚠️ For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under AGPL-3.0 and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/env.md) for more information.
## Getting started 🔧
+9
View File
@@ -16,6 +16,15 @@ the following command inside your docker container:
## [Unreleased]
## [3.3.0] - 2025-05-22
⚠️ For some advanced features (ex: Export as PDF) Docs relies on XL packages from BlockNote. These are licenced under AGPL-3.0 and are not MIT compatible. You can perfectly use Docs without these packages by setting the environment variable `PUBLISH_AS_MIT` to true. That way you'll build an image of the application without the features that are not MIT compatible. Read the [environment variables documentation](/docs/docs/env.md) for more information.
The footer is now configurable from a customization file. To override the default one, you can
use the `THEME_CUSTOMIZATION_FILE_PATH` environment variable to point to your customization file.
The customization file must be a JSON file and must follow the rules described in the
[theming documentation](docs/theming.md).
## [3.0.0] - 2025-03-28
We are not using the nginx auth request anymore to access the collaboration server (`yProvider`)
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+1
View File
@@ -47,6 +47,7 @@ These are the environment variables you can set for the `impress-backend` contai
| COLLABORATION_API_URL | collaboration api host | |
| COLLABORATION_SERVER_SECRET | collaboration api secret | |
| COLLABORATION_WS_URL | collaboration websocket url | |
| COLLABORATION_WS_NOT_CONNECTED_READY_ONLY | Users not connected to the collaboration server cannot edit | false |
| FRONTEND_CSS_URL | To add a external css file to the app | |
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false |
| FRONTEND_THEME | frontend theme to use | |
+23
View File
@@ -30,4 +30,27 @@ body {
Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom CSS file. Once you've done this, our application will load your custom CSS file and apply the styles, changing the background color to the custom color you specified.
----
# **Footer Configuration** 📝
The footer is configurable from the theme customization file.
### Settings 🔧
```shellscript
THEME_CUSTOMIZATION_FILE_PATH=<path>
```
### Example of JSON
The json must follow some rules: https://github.com/suitenumerique/docs/blob/main/src/helm/env.d/dev/configuration/theme/demo.json
`footer.default` is the fallback if the language is not supported.
---
Below is a visual example of a configured footer ⬇️:
![Footer Configuration Example](./assets/footer-configurable.png)
-3
View File
@@ -61,6 +61,3 @@ COLLABORATION_BACKEND_BASE_URL=http://app-dev:8000
COLLABORATION_SERVER_ORIGIN=http://localhost:3000
COLLABORATION_SERVER_SECRET=my-secret
COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
# Frontend
FRONTEND_THEME=default
+1 -2
View File
@@ -1,7 +1,6 @@
# For the CI job test-e2e
SUSTAINED_THROTTLE_RATES="200/hour"
BURST_THROTTLE_RATES="200/minute"
DJANGO_SERVER_TO_SERVER_API_TOKENS=test-e2e
SUSTAINED_THROTTLE_RATES="200/hour"
Y_PROVIDER_API_KEY=yprovider-api-key
Y_PROVIDER_API_BASE_URL=http://y-provider:4444/api/
THEME_CUSTOMIZATION_FILE_PATH="" #force theme_customization to be empty
+60 -3
View File
@@ -4,7 +4,7 @@
import json
import logging
import uuid
from urllib.parse import unquote, urlparse
from urllib.parse import unquote, urlencode, urlparse
from django.conf import settings
from django.contrib.postgres.aggregates import ArrayAgg
@@ -18,6 +18,7 @@ from django.db import models as db
from django.db.models.expressions import RawSQL
from django.db.models.functions import Left, Length
from django.http import Http404, StreamingHttpResponse
from django.urls import reverse
from django.utils.text import capfirst, slugify
from django.utils.translation import gettext_lazy as _
@@ -1193,8 +1194,16 @@ class DocumentViewSet(
malware_detection.analyse_file(key, document_id=document.id)
url = reverse(
"documents-media-check",
kwargs={"pk": document.id},
)
parameters = urlencode({"key": key})
return drf.response.Response(
{"file": f"{settings.MEDIA_URL:s}{key:s}"},
{
"file": f"{url:s}?{parameters:s}",
},
status=drf.status.HTTP_201_CREATED,
)
@@ -1279,7 +1288,10 @@ class DocumentViewSet(
# Check if the attachment is ready
s3_client = default_storage.connection.meta.client
bucket_name = default_storage.bucket_name
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
try:
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
except ClientError as err:
raise drf.exceptions.PermissionDenied() from err
metadata = head_resp.get("Metadata", {})
# In order to be compatible with existing upload without `status` metadata,
# we consider them as ready.
@@ -1294,6 +1306,50 @@ class DocumentViewSet(
return drf.response.Response("authorized", headers=request.headers, status=200)
@drf.decorators.action(detail=True, methods=["get"], url_path="media-check")
def media_check(self, request, *args, **kwargs):
"""
Check if the media is ready to be served.
"""
document = self.get_object()
key = request.query_params.get("key")
if not key:
return drf.response.Response(
{"detail": "Missing 'key' query parameter"},
status=drf.status.HTTP_400_BAD_REQUEST,
)
if key not in document.attachments:
return drf.response.Response(
{"detail": "Attachment missing"},
status=drf.status.HTTP_404_NOT_FOUND,
)
# Check if the attachment is ready
s3_client = default_storage.connection.meta.client
bucket_name = default_storage.bucket_name
try:
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
except ClientError as err:
logger.error("Client Error fetching file %s metadata: %s", key, err)
return drf.response.Response(
{"detail": "Media not found"},
status=drf.status.HTTP_404_NOT_FOUND,
)
metadata = head_resp.get("Metadata", {})
body = {
"status": metadata.get("status", enums.DocumentAttachmentStatus.PROCESSING),
}
if metadata.get("status") == enums.DocumentAttachmentStatus.READY:
body = {
"status": enums.DocumentAttachmentStatus.READY,
"file": f"{settings.MEDIA_URL:s}{key:s}",
}
return drf.response.Response(body, status=drf.status.HTTP_200_OK)
@drf.decorators.action(
detail=True,
methods=["post"],
@@ -1729,6 +1785,7 @@ class ConfigView(drf.views.APIView):
array_settings = [
"AI_FEATURE_ENABLED",
"COLLABORATION_WS_URL",
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY",
"CRISP_WEBSITE_ID",
"ENVIRONMENT",
"FRONTEND_CSS_URL",
+1
View File
@@ -835,6 +835,7 @@ class Document(MP_Node, BaseModel):
"ai_transform": ai_access,
"ai_translate": ai_access,
"attachment_upload": can_update,
"media_check": can_get,
"children_list": can_get,
"children_create": can_update and user.is_authenticated,
"collaboration_auth": can_get,
@@ -5,6 +5,7 @@ Test file uploads API endpoint for users in impress's core app.
import re
import uuid
from unittest import mock
from urllib.parse import parse_qs, urlparse
from django.core.files.storage import default_storage
from django.core.files.uploadedfile import SimpleUploadedFile
@@ -66,8 +67,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"]
pattern = re.compile(rf"^{document.id!s}/attachments/(.*)\.png")
url_parsed = urlparse(response.json()["file"])
assert url_parsed.path == f"/api/v1.0/documents/{document.id!s}/media-check/"
query = parse_qs(url_parsed.query)
assert query["key"][0] is not None
file_path = query["key"][0]
match = pattern.search(file_path)
file_id = match.group(1)
# Validate that file_id is a valid UUID
@@ -148,8 +153,13 @@ def test_api_documents_attachment_upload_authenticated_success(reach, role):
assert response.status_code == 201
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.png")
match = pattern.search(response.json()["file"])
pattern = re.compile(rf"^{document.id!s}/attachments/(.*)\.png")
url_parsed = urlparse(response.json()["file"])
assert url_parsed.path == f"/api/v1.0/documents/{document.id!s}/media-check/"
query = parse_qs(url_parsed.query)
assert query["key"][0] is not None
file_path = query["key"][0]
match = pattern.search(file_path)
file_id = match.group(1)
mock_analyse_file.assert_called_once_with(
@@ -224,8 +234,12 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
assert response.status_code == 201
file_path = response.json()["file"]
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.png")
pattern = re.compile(rf"^{document.id!s}/attachments/(.*)\.png")
url_parsed = urlparse(response.json()["file"])
assert url_parsed.path == f"/api/v1.0/documents/{document.id!s}/media-check/"
query = parse_qs(url_parsed.query)
assert query["key"][0] is not None
file_path = query["key"][0]
match = pattern.search(file_path)
file_id = match.group(1)
@@ -320,8 +334,13 @@ def test_api_documents_attachment_upload_fix_extension(
assert response.status_code == 201
file_path = response.json()["file"]
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.{extension:s}")
pattern = re.compile(rf"^{document.id!s}/attachments/(.*)\.{extension:s}")
url_parsed = urlparse(response.json()["file"])
assert url_parsed.path == f"/api/v1.0/documents/{document.id!s}/media-check/"
query = parse_qs(url_parsed.query)
assert query["key"][0] is not None
file_path = query["key"][0]
match = pattern.search(file_path)
file_id = match.group(1)
@@ -386,8 +405,12 @@ def test_api_documents_attachment_upload_unsafe():
assert response.status_code == 201
file_path = response.json()["file"]
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.exe")
pattern = re.compile(rf"^{document.id!s}/attachments/(.*)\.exe")
url_parsed = urlparse(response.json()["file"])
assert url_parsed.path == f"/api/v1.0/documents/{document.id!s}/media-check/"
query = parse_qs(url_parsed.query)
assert query["key"][0] is not None
file_path = query["key"][0]
match = pattern.search(file_path)
file_id = match.group(1)
@@ -0,0 +1,244 @@
"""Test the "media_check" endpoint."""
from io import BytesIO
from uuid import uuid4
from django.core.files.storage import default_storage
import pytest
from rest_framework.test import APIClient
from core import factories
from core.enums import DocumentAttachmentStatus
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db
def test_api_documents_media_check_unknown_document():
"""
The "media_check" endpoint should return a 404 error if the document does not exist.
"""
client = APIClient()
response = client.get(f"/api/v1.0/documents/{uuid4()!s}media-check/")
assert response.status_code == 404
def test_api_documents_media_check_missing_key():
"""
The "media_check" endpoint should return a 404 error if the key is missing.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user=user)
document = factories.DocumentFactory(users=[user])
response = client.get(f"/api/v1.0/documents/{document.id!s}/media-check/")
assert response.status_code == 400
assert response.json() == {"detail": "Missing 'key' query parameter"}
def test_api_documents_media_check_key_parameter_not_related_to_document():
"""
The "media_check" endpoint should return a 404 error if the key is not related to the document.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user=user)
document = factories.DocumentFactory(users=[user])
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/",
{"key": f"{document.id!s}/attachments/unknown.jpg"},
)
assert response.status_code == 404
assert response.json() == {"detail": "Attachment missing"}
def test_api_documents_media_check_anonymous_public_document():
"""
The "media_check" endpoint should return a 200 status code if the document is public.
"""
document = factories.DocumentFactory(link_reach="public")
filename = f"{uuid4()!s}.jpg"
key = f"{document.id!s}/attachments/{filename:s}"
default_storage.connection.meta.client.put_object(
Bucket=default_storage.bucket_name,
Key=key,
Body=BytesIO(b"my prose"),
ContentType="text/plain",
Metadata={"status": DocumentAttachmentStatus.PROCESSING},
)
document.attachments = [key]
document.save(update_fields=["attachments"])
client = APIClient()
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/", {"key": key}
)
assert response.status_code == 200
assert response.json() == {"status": DocumentAttachmentStatus.PROCESSING}
def test_api_documents_media_check_anonymous_public_document_ready():
"""
The "media_check" endpoint should return a 200 status code if the document is public.
"""
document = factories.DocumentFactory(link_reach="public")
filename = f"{uuid4()!s}.jpg"
key = f"{document.id!s}/attachments/{filename:s}"
default_storage.connection.meta.client.put_object(
Bucket=default_storage.bucket_name,
Key=key,
Body=BytesIO(b"my prose"),
ContentType="text/plain",
Metadata={"status": DocumentAttachmentStatus.READY},
)
document.attachments = [key]
document.save(update_fields=["attachments"])
client = APIClient()
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/", {"key": key}
)
assert response.status_code == 200
assert response.json() == {
"status": DocumentAttachmentStatus.READY,
"file": f"/media/{key:s}",
}
@pytest.mark.parametrize("link_reach", ["restricted", "authenticated"])
def test_api_documents_media_check_anonymous_non_public_document(link_reach):
"""
The "media_check" endpoint should return a 403 error if the document is not public.
"""
document = factories.DocumentFactory(link_reach=link_reach)
client = APIClient()
response = client.get(f"/api/v1.0/documents/{document.id!s}/media-check/")
assert response.status_code == 401
def test_api_documents_media_check_connected_document():
"""
The "media_check" endpoint should return a 200 status code for a user connected
checking for a document with link_reach authenticated.
"""
document = factories.DocumentFactory(link_reach="authenticated")
filename = f"{uuid4()!s}.jpg"
key = f"{document.id!s}/attachments/{filename:s}"
default_storage.connection.meta.client.put_object(
Bucket=default_storage.bucket_name,
Key=key,
Body=BytesIO(b"my prose"),
ContentType="text/plain",
Metadata={"status": DocumentAttachmentStatus.READY},
)
document.attachments = [key]
document.save(update_fields=["attachments"])
user = factories.UserFactory()
client = APIClient()
client.force_login(user=user)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/", {"key": key}
)
assert response.status_code == 200
assert response.json() == {
"status": DocumentAttachmentStatus.READY,
"file": f"/media/{key:s}",
}
def test_api_documents_media_check_connected_document_media_not_related():
"""
The "media_check" endpoint should return a 404 error if the key is not related to the document.
"""
document = factories.DocumentFactory(link_reach="authenticated")
filename = f"{uuid4()!s}.jpg"
key = f"{document.id!s}/attachments/{filename:s}"
user = factories.UserFactory()
client = APIClient()
client.force_login(user=user)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/", {"key": key}
)
assert response.status_code == 404
assert response.json() == {"detail": "Attachment missing"}
def test_api_documents_media_check_media_missing_on_storage():
"""
The "media_check" endpoint should return a 404 error if the media is missing on storage.
"""
document = factories.DocumentFactory(link_reach="authenticated")
filename = f"{uuid4()!s}.jpg"
key = f"{document.id!s}/attachments/{filename:s}"
document.attachments = [key]
document.save(update_fields=["attachments"])
user = factories.UserFactory()
client = APIClient()
client.force_login(user=user)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/", {"key": key}
)
assert response.status_code == 404
assert response.json() == {"detail": "Media not found"}
@pytest.mark.parametrize("via", VIA)
def test_api_documents_media_check_restricted_document(via, mock_user_teams):
"""
The "media_check" endpoint should return a 200 status code if the document is restricted and
the user has access to it.
"""
document = factories.DocumentFactory(link_reach="restricted")
filename = f"{uuid4()!s}.jpg"
key = f"{document.id!s}/attachments/{filename:s}"
default_storage.connection.meta.client.put_object(
Bucket=default_storage.bucket_name,
Key=key,
Body=BytesIO(b"my prose"),
ContentType="text/plain",
Metadata={"status": DocumentAttachmentStatus.READY},
)
document.attachments = [key]
document.save(update_fields=["attachments"])
user = factories.UserFactory()
client = APIClient()
client.force_login(user=user)
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user)
elif via == TEAM:
mock_user_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(document=document, team="lasuite")
response = client.get(
f"/api/v1.0/documents/{document.id!s}/media-check/", {"key": key}
)
assert response.status_code == 200
assert response.json() == {
"status": DocumentAttachmentStatus.READY,
"file": f"/media/{key:s}",
}
@@ -48,6 +48,7 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": document.link_role == "editor",
"restore": False,
@@ -111,6 +112,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
"link_configuration": False,
"link_select_options": models.LinkReachChoices.get_select_options(links),
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": grand_parent.link_role == "editor",
"restore": False,
@@ -210,6 +212,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": document.link_role == "editor",
"restore": False,
@@ -279,8 +282,9 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
"invite_owner": False,
"link_configuration": False,
"link_select_options": models.LinkReachChoices.get_select_options(links),
"move": False,
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": grand_parent.link_role == "editor",
"restore": False,
"retrieve": True,
@@ -460,6 +464,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
"link_configuration": access.role in ["administrator", "owner"],
"link_select_options": models.LinkReachChoices.get_select_options(links),
"media_auth": True,
"media_check": True,
"move": access.role in ["administrator", "owner"],
"partial_update": access.role != "reader",
"restore": access.role == "owner",
@@ -91,6 +91,7 @@ def test_api_documents_trashbin_format():
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False, # Can't move a deleted document
"partial_update": True,
"restore": True,
@@ -20,6 +20,7 @@ pytestmark = pytest.mark.django_db
@override_settings(
AI_FEATURE_ENABLED=False,
COLLABORATION_WS_URL="http://testcollab/",
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=True,
CRISP_WEBSITE_ID="123",
FRONTEND_CSS_URL="http://testcss/",
FRONTEND_THEME="test-theme",
@@ -41,6 +42,7 @@ def test_api_config(is_authenticated):
assert response.status_code == HTTP_200_OK
assert response.json() == {
"COLLABORATION_WS_URL": "http://testcollab/",
"COLLABORATION_WS_NOT_CONNECTED_READY_ONLY": True,
"CRISP_WEBSITE_ID": "123",
"ENVIRONMENT": "test",
"FRONTEND_CSS_URL": "http://testcss/",
@@ -165,6 +165,7 @@ def test_models_documents_get_abilities_forbidden(
"favorite": False,
"invite_owner": False,
"media_auth": False,
"media_check": False,
"move": False,
"link_configuration": False,
"link_select_options": {
@@ -231,6 +232,7 @@ def test_models_documents_get_abilities_reader(
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": False,
"restore": False,
@@ -293,6 +295,7 @@ def test_models_documents_get_abilities_editor(
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": True,
"restore": False,
@@ -344,6 +347,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": True,
"partial_update": True,
"restore": True,
@@ -392,6 +396,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": True,
"partial_update": True,
"restore": False,
@@ -443,6 +448,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": True,
"restore": False,
@@ -501,6 +507,7 @@ def test_models_documents_get_abilities_reader_user(
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": access_from_link,
"restore": False,
@@ -557,6 +564,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
"restricted": ["reader", "editor"],
},
"media_auth": True,
"media_check": True,
"move": False,
"partial_update": False,
"restore": False,
@@ -1,124 +1,129 @@
{
"footer": {
"default": {
"externalLinks": [
{
"label": "Github",
"href": "https://github.com/suitenumerique/docs/"
},
{
"label": "DINUM",
"href": "https://www.numerique.gouv.fr/dinum/"
},
{
"label": "ZenDiS",
"href": "https://zendis.de/"
},
{
"label": "BlockNote.js",
"href": "https://www.blocknotejs.org/"
}
],
"bottomInformation": {
"label": "Unless otherwise stated, all content on this site is under",
"link": {
"label": "licence etalab-2.0",
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
}
}
"footer": {
"default": {
"logo": {
"src": "/assets/icon-docs.svg",
"width": "54px",
"alt": "Docs Logo",
"withTitle": true
},
"en": {
"legalLinks": [
{
"label": "Legal Notice",
"href": "#"
},
{
"label": "Personal data and cookies",
"href": "#"
},
{
"label": "Accessibility",
"href": "#"
}
],
"bottomInformation": {
"label": "Unless otherwise stated, all content on this site is under",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
"externalLinks": [
{
"label": "Github",
"href": "https://github.com/suitenumerique/docs/"
},
{
"label": "DINUM",
"href": "https://www.numerique.gouv.fr/dinum/"
},
{
"label": "ZenDiS",
"href": "https://zendis.de/"
},
{
"label": "BlockNote.js",
"href": "https://www.blocknotejs.org/"
}
},
"fr": {
"legalLinks": [
{
"label": "Mentions légales",
"href": "#"
},
{
"label": "Données personnelles et cookies",
"href": "#"
},
{
"label": "Accessibilité",
"href": "#"
}
],
"bottomInformation": {
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
],
"bottomInformation": {
"label": "Unless otherwise stated, all content on this site is under",
"link": {
"label": "licence etalab-2.0",
"href": "https://github.com/etalab/licence-ouverte/blob/master/LO.md"
}
},
"de": {
"legalLinks": [
{
"label": "Impressum",
"href": "#"
},
{
"label": "Personenbezogene Daten und Cookies",
"href": "#"
},
{
"label": "Barrierefreiheit",
"href": "#"
}
],
"bottomInformation": {
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
}
},
"en": {
"legalLinks": [
{
"label": "Legal Notice",
"href": "#"
},
{
"label": "Personal data and cookies",
"href": "#"
},
{
"label": "Accessibility",
"href": "#"
}
},
"nl": {
"legalLinks": [
{
"label": "Wettelijke bepalingen",
"href": "#"
},
{
"label": "Persoonlijke gegevens en cookies",
"href": "#"
},
{
"label": "Toegankelijkheid",
"href": "#"
}
],
"bottomInformation": {
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
],
"bottomInformation": {
"label": "Unless otherwise stated, all content on this site is under",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
}
},
"fr": {
"legalLinks": [
{
"label": "Mentions légales",
"href": "#"
},
{
"label": "Données personnelles et cookies",
"href": "#"
},
{
"label": "Accessibilité",
"href": "#"
}
],
"bottomInformation": {
"label": "Sauf mention contraire, tout le contenu de ce site est sous",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
}
},
"de": {
"legalLinks": [
{
"label": "Impressum",
"href": "#"
},
{
"label": "Personenbezogene Daten und Cookies",
"href": "#"
},
{
"label": "Barrierefreiheit",
"href": "#"
}
],
"bottomInformation": {
"label": "Sofern nicht anders angegeben, steht der gesamte Inhalt dieser Website unter",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
}
},
"nl": {
"legalLinks": [
{
"label": "Wettelijke bepalingen",
"href": "#"
},
{
"label": "Persoonlijke gegevens en cookies",
"href": "#"
},
{
"label": "Toegankelijkheid",
"href": "#"
}
],
"bottomInformation": {
"label": "Tenzij anders vermeld, is alle inhoud van deze site ondergebracht onder",
"link": {
"label": "licence MIT",
"href": "https://github.com/suitenumerique/docs/blob/main/LICENSE"
}
}
}
}
}
+14
View File
@@ -413,6 +413,11 @@ class Base(Configuration):
COLLABORATION_WS_URL = values.Value(
None, environ_name="COLLABORATION_WS_URL", environ_prefix=None
)
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = values.BooleanValue(
False,
environ_name="COLLABORATION_WS_NOT_CONNECTED_READY_ONLY",
environ_prefix=None,
)
# Frontend
FRONTEND_THEME = values.Value(
@@ -679,6 +684,15 @@ class Base(Configuration):
),
"propagate": False,
},
"docs.security": {
"handlers": ["console"],
"level": values.Value(
"INFO",
environ_name="LOGGING_LEVEL_LOGGERS_SECURITY",
environ_prefix=None,
),
"propagate": False,
},
},
}
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Breton\n"
"Language: br_FR\n"
@@ -66,32 +66,32 @@ msgstr "Doare korf"
msgid "Format"
msgstr "Stumm"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "eilenn {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "Bugel kentañ"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "Bugel diwezhañ"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Kleiz"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Dehoù"
@@ -225,8 +225,8 @@ msgstr "implijer"
msgid "users"
msgstr "implijerien"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "titl"
@@ -242,128 +242,128 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "publik"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Patrom"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Patromoù"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr ""
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -66,32 +66,32 @@ msgstr "Typ"
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "Kopie von {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "Erstes Unterelement"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "Letztes Unterelement"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr "Erstes Nebenelement"
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr "Letztes Nebenelement"
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Links"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Rechts"
@@ -225,8 +225,8 @@ msgstr "Benutzer"
msgid "users"
msgstr "Benutzer"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "Titel"
@@ -242,128 +242,128 @@ msgstr "Dokument"
msgid "Documents"
msgstr "Dokumente"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "Unbenanntes Dokument"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, 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:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, 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:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr "Für dieses Dokument/ diesen Benutzer ist bereits eine Linkverfolgung vorhanden."
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "Dokumentenfavorit"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "Dokumentfavoriten"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
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:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr "Dokument/Benutzerbeziehung"
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr "Dokument/Benutzerbeziehungen"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
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:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "Beschreibung"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "Code"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "öffentlich"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Vorlage"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Vorlagen"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr "Vorlage/Benutzer-Beziehung"
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr "Vorlage/Benutzerbeziehungen"
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "Dieses Team ist bereits in diesem Template."
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "E-Mail-Adresse"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Einladung zum Dokument"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Dokumenteinladungen"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -66,32 +66,32 @@ msgstr ""
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr ""
@@ -242,128 +242,128 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr ""
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -66,32 +66,32 @@ msgstr "Tipo de Cuerpo"
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "copia de {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "Primer nodo"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "Último nodo"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr "Primera relación"
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr "Última relación"
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Izquierda"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Derecha"
@@ -225,8 +225,8 @@ msgstr "usuario"
msgid "users"
msgstr "usuarios"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "título"
@@ -242,128 +242,128 @@ msgstr "Documento"
msgid "Documents"
msgstr "Documentos"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "Documento sin título"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "¡{name} ha compartido un documento contigo!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "Te ha invitado {name} al siguiente documento con el rol \"{role}\" :"
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} ha compartido un documento contigo: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr "Traza del enlace de documento/usuario"
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr "Trazas del enlace de documento/usuario"
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr "Ya existe una traza de enlace para este documento/usuario."
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "Documento favorito"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "Documentos favoritos"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Este documento ya ha sido marcado como favorito por el usuario."
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr "Relación documento/usuario"
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr "Relaciones documento/usuario"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "Este usuario ya forma parte del documento."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "Este equipo ya forma parte del documento."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr "Debe establecerse un usuario o un equipo, no ambos."
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "descripción"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "código"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "público"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "Si esta plantilla es pública para que cualquiera la utilice."
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Plantilla"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Plantillas"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr "Relación plantilla/usuario"
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr "Relaciones plantilla/usuario"
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "Este usuario ya forma parte de la plantilla."
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "Este equipo ya se encuentra en esta plantilla."
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "dirección de correo electrónico"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Invitación al documento"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Invitaciones a documentos"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Este correo electrónico está asociado a un usuario registrado."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 09:05\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -66,32 +66,32 @@ msgstr "Type de corps"
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "copie de {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "Premier enfant"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "Dernier enfant"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr "Premier frère ou sœur"
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr "Dernière relation"
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Gauche"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Droite"
@@ -225,8 +225,8 @@ msgstr "utilisateur"
msgid "users"
msgstr "utilisateurs"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "titre"
@@ -242,128 +242,128 @@ msgstr "Document"
msgid "Documents"
msgstr "Documents"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "Document sans titre"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} a partagé un document avec vous!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, 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:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, 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:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr "Trace du lien document/utilisateur"
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr "Traces du lien document/utilisateur"
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr "Une trace de lien existe déjà pour ce document/utilisateur."
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "Document favori"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "Documents favoris"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Ce document est déjà un favori de cet utilisateur."
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr "Relation document/utilisateur"
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr "Relations document/utilisateur"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "Cet utilisateur est déjà dans ce document."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "Cette équipe est déjà dans ce document."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr "L'utilisateur ou l'équipe doivent être définis, pas les deux."
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "description"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "public"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "Si ce modèle est public, utilisable par n'importe qui."
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Modèle"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Modèles"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr "Relation modèle/utilisateur"
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr "Relations modèle/utilisateur"
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "Cet utilisateur est déjà dans ce modèle."
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "Cette équipe est déjà modèle."
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "adresse e-mail"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Invitation à un document"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Invitations à un document"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -66,32 +66,32 @@ msgstr ""
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "copia di {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Sinistra"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Destra"
@@ -225,8 +225,8 @@ msgstr "utente"
msgid "users"
msgstr "utenti"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "titolo"
@@ -242,128 +242,128 @@ msgstr "Documento"
msgid "Documents"
msgstr "Documenti"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "Documento senza titolo"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} ha condiviso un documento con te!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} ti ha invitato con il ruolo \"{role}\" nel seguente documento:"
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} ha condiviso un documento con te: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "Documento preferito"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "Documenti preferiti"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "Questo utente è già presente in questo documento."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "Questo team è già presente in questo documento."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "descrizione"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "pubblico"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "Indica se questo modello è pubblico per chiunque."
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Modello"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Modelli"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "Questo utente è già in questo modello."
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "Questo team è già in questo modello."
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "indirizzo e-mail"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Invito al documento"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Inviti al documento"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Questa email è già associata a un utente registrato."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -66,32 +66,32 @@ msgstr "Text type"
msgid "Format"
msgstr "Formaat"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "kopie van {title}"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "Eerste node"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "Laatste node"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr "Eerste naaste"
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr "Laatste naaste"
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Links"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Rechts"
@@ -225,8 +225,8 @@ msgstr "gebruiker"
msgid "users"
msgstr "gebruikers"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "titel"
@@ -242,128 +242,128 @@ msgstr "Document"
msgid "Documents"
msgstr "Documenten"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "Naamloos Document"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} heeft een document met gedeeld!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} heeft u uitgenodigd met de rol \"{role}\" op het volgende document:"
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} heeft een document met u gedeeld: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr "Document/gebruiker url"
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr "Document/gebruiker url"
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr "Een url bestaat al voor dit document/deze gebruiker."
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "Document favoriet"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "Document favorieten"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Dit document is al in gebruik als favoriete door dezelfde gebruiker."
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr "Document/gebruiker relatie"
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr "Document/gebruiker relaties"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "De gebruiker is al in dit document."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "Het team is al in dit document."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr "Een gebruiker of team moet gekozen worden, maar niet beide."
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "omschrijving"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "publiek"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "Of dit template als publiek is en door iedereen te gebruiken is."
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Template"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Templates"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr "Template/gebruiker relatie"
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr "Template/gebruiker relaties"
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "De gebruiker bestaat al in dit template."
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "Het team bestaat al in dit template."
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "email adres"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Document uitnodiging"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Document uitnodigingen"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -66,32 +66,32 @@ msgstr ""
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr ""
@@ -242,128 +242,128 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr ""
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Language: sl_SI\n"
@@ -66,32 +66,32 @@ msgstr "Vrsta telesa"
msgid "Format"
msgstr "Oblika"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "Prvi otrok"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "Zadnji otrok"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr "Prvi brat in sestra"
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr "Zadnji brat in sestra"
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "Levo"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "Desno"
@@ -225,8 +225,8 @@ msgstr "uporabnik"
msgid "users"
msgstr "uporabniki"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "naslov"
@@ -242,128 +242,128 @@ msgstr "Dokument"
msgid "Documents"
msgstr "Dokumenti"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "Dokument brez naslova"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} je delil dokument z vami!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} vas je povabil z vlogo \"{role}\" na naslednjem dokumentu:"
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} je delil dokument z vami: {title}"
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr "Dokument/sled povezave uporabnika"
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr "Sledi povezav dokumenta/uporabnika"
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr "Za ta dokument/uporabnika že obstaja sled povezave."
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "Priljubljeni dokument"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "Priljubljeni dokumenti"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Ta dokument je že ciljno usmerjen s priljubljenim primerkom relacije za istega uporabnika."
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr "Odnos dokument/uporabnik"
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr "Odnosi dokument/uporabnik"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "Ta uporabnik je že v tem dokumentu."
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "Ta ekipa je že v tem dokumentu."
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr "Nastaviti je treba bodisi uporabnika ali ekipo, a ne obojega."
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "opis"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "koda"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "javno"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "Ali je ta predloga javna za uporabo."
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "Predloga"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "Predloge"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr "Odnos predloga/uporabnik"
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr "Odnosi med predlogo in uporabnikom"
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "Ta uporabnik je že v tej predlogi."
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "Ta ekipa je že v tej predlogi."
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "elektronski naslov"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Vabilo na dokument"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Vabila na dokument"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Ta e-poštni naslov je že povezan z registriranim uporabnikom."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
@@ -66,32 +66,32 @@ msgstr ""
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr ""
@@ -242,128 +242,128 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "e-postadress"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "Bjud in dokument"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "Inbjudningar dokument"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "Denna e-postadress är redan associerad med en registrerad användare."
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
@@ -66,32 +66,32 @@ msgstr ""
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr ""
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr ""
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr ""
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr ""
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr ""
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr ""
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr ""
@@ -242,128 +242,128 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr ""
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr ""
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr ""
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr ""
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr ""
+43 -43
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-29 11:48+0000\n"
"PO-Revision-Date: 2025-05-05 07:07\n"
"POT-Creation-Date: 2025-05-22 12:09+0000\n"
"PO-Revision-Date: 2025-05-22 14:16\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -66,32 +66,32 @@ msgstr "正文类型"
msgid "Format"
msgstr "格式"
#: build/lib/core/api/viewsets.py:966 core/api/viewsets.py:966
#: build/lib/core/api/viewsets.py:967 core/api/viewsets.py:967
#, python-brace-format
msgid "copy of {title}"
msgstr "{title} 的副本"
#: build/lib/core/enums.py:35 core/enums.py:35
#: build/lib/core/enums.py:36 core/enums.py:36
msgid "First child"
msgstr "第一个子项"
#: build/lib/core/enums.py:36 core/enums.py:36
#: build/lib/core/enums.py:37 core/enums.py:37
msgid "Last child"
msgstr "最后一个子项"
#: build/lib/core/enums.py:37 core/enums.py:37
#: build/lib/core/enums.py:38 core/enums.py:38
msgid "First sibling"
msgstr "第一个同级项"
#: build/lib/core/enums.py:38 core/enums.py:38
#: build/lib/core/enums.py:39 core/enums.py:39
msgid "Last sibling"
msgstr "最后一个同级项"
#: build/lib/core/enums.py:39 core/enums.py:39
#: build/lib/core/enums.py:40 core/enums.py:40
msgid "Left"
msgstr "左"
#: build/lib/core/enums.py:40 core/enums.py:40
#: build/lib/core/enums.py:41 core/enums.py:41
msgid "Right"
msgstr "右"
@@ -225,8 +225,8 @@ msgstr "用户"
msgid "users"
msgstr "个用户"
#: build/lib/core/models.py:470 build/lib/core/models.py:1154
#: core/models.py:470 core/models.py:1154
#: build/lib/core/models.py:470 build/lib/core/models.py:1155
#: core/models.py:470 core/models.py:1155
msgid "title"
msgstr "标题"
@@ -242,128 +242,128 @@ msgstr "文档"
msgid "Documents"
msgstr "个文档"
#: build/lib/core/models.py:532 build/lib/core/models.py:872 core/models.py:532
#: core/models.py:872
#: build/lib/core/models.py:532 build/lib/core/models.py:873 core/models.py:532
#: core/models.py:873
msgid "Untitled Document"
msgstr "未命名文档"
#: build/lib/core/models.py:907 core/models.py:907
#: build/lib/core/models.py:908 core/models.py:908
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} 与您共享了一个文档!"
#: build/lib/core/models.py:911 core/models.py:911
#: build/lib/core/models.py:912 core/models.py:912
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} 邀请您以“{role}”角色访问以下文档:"
#: build/lib/core/models.py:917 core/models.py:917
#: build/lib/core/models.py:918 core/models.py:918
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} 与您共享了一个文档:{title}"
#: build/lib/core/models.py:1015 core/models.py:1015
#: build/lib/core/models.py:1016 core/models.py:1016
msgid "Document/user link trace"
msgstr "文档/用户链接跟踪"
#: build/lib/core/models.py:1016 core/models.py:1016
#: build/lib/core/models.py:1017 core/models.py:1017
msgid "Document/user link traces"
msgstr "个文档/用户链接跟踪"
#: build/lib/core/models.py:1022 core/models.py:1022
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "A link trace already exists for this document/user."
msgstr "此文档/用户的链接跟踪已存在。"
#: build/lib/core/models.py:1045 core/models.py:1045
#: build/lib/core/models.py:1046 core/models.py:1046
msgid "Document favorite"
msgstr "文档收藏"
#: build/lib/core/models.py:1046 core/models.py:1046
#: build/lib/core/models.py:1047 core/models.py:1047
msgid "Document favorites"
msgstr "文档收藏夹"
#: build/lib/core/models.py:1052 core/models.py:1052
#: build/lib/core/models.py:1053 core/models.py:1053
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "该文档已被同一用户的收藏关系实例关联。"
#: build/lib/core/models.py:1074 core/models.py:1074
#: build/lib/core/models.py:1075 core/models.py:1075
msgid "Document/user relation"
msgstr "文档/用户关系"
#: build/lib/core/models.py:1075 core/models.py:1075
#: build/lib/core/models.py:1076 core/models.py:1076
msgid "Document/user relations"
msgstr "文档/用户关系集"
#: build/lib/core/models.py:1081 core/models.py:1081
#: build/lib/core/models.py:1082 core/models.py:1082
msgid "This user is already in this document."
msgstr "该用户已在此文档中。"
#: build/lib/core/models.py:1087 core/models.py:1087
#: build/lib/core/models.py:1088 core/models.py:1088
msgid "This team is already in this document."
msgstr "该团队已在此文档中。"
#: build/lib/core/models.py:1093 build/lib/core/models.py:1241
#: core/models.py:1093 core/models.py:1241
#: build/lib/core/models.py:1094 build/lib/core/models.py:1242
#: core/models.py:1094 core/models.py:1242
msgid "Either user or team must be set, not both."
msgstr "必须设置用户或团队之一,不能同时设置两者。"
#: build/lib/core/models.py:1155 core/models.py:1155
#: build/lib/core/models.py:1156 core/models.py:1156
msgid "description"
msgstr "说明"
#: build/lib/core/models.py:1156 core/models.py:1156
#: build/lib/core/models.py:1157 core/models.py:1157
msgid "code"
msgstr "代码"
#: build/lib/core/models.py:1157 core/models.py:1157
#: build/lib/core/models.py:1158 core/models.py:1158
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1159 core/models.py:1159
#: build/lib/core/models.py:1160 core/models.py:1160
msgid "public"
msgstr "公开"
#: build/lib/core/models.py:1161 core/models.py:1161
#: build/lib/core/models.py:1162 core/models.py:1162
msgid "Whether this template is public for anyone to use."
msgstr "该模板是否公开供任何人使用。"
#: build/lib/core/models.py:1167 core/models.py:1167
#: build/lib/core/models.py:1168 core/models.py:1168
msgid "Template"
msgstr "模板"
#: build/lib/core/models.py:1168 core/models.py:1168
#: build/lib/core/models.py:1169 core/models.py:1169
msgid "Templates"
msgstr "模板"
#: build/lib/core/models.py:1222 core/models.py:1222
#: build/lib/core/models.py:1223 core/models.py:1223
msgid "Template/user relation"
msgstr "模板/用户关系"
#: build/lib/core/models.py:1223 core/models.py:1223
#: build/lib/core/models.py:1224 core/models.py:1224
msgid "Template/user relations"
msgstr "模板/用户关系集"
#: build/lib/core/models.py:1229 core/models.py:1229
#: build/lib/core/models.py:1230 core/models.py:1230
msgid "This user is already in this template."
msgstr "该用户已在此模板中。"
#: build/lib/core/models.py:1235 core/models.py:1235
#: build/lib/core/models.py:1236 core/models.py:1236
msgid "This team is already in this template."
msgstr "该团队已在此模板中。"
#: build/lib/core/models.py:1258 core/models.py:1258
#: build/lib/core/models.py:1259 core/models.py:1259
msgid "email address"
msgstr "电子邮件地址"
#: build/lib/core/models.py:1277 core/models.py:1277
#: build/lib/core/models.py:1278 core/models.py:1278
msgid "Document invitation"
msgstr "文档邀请"
#: build/lib/core/models.py:1278 core/models.py:1278
#: build/lib/core/models.py:1279 core/models.py:1279
msgid "Document invitations"
msgstr "文档邀请"
#: build/lib/core/models.py:1298 core/models.py:1298
#: build/lib/core/models.py:1299 core/models.py:1299
msgid "This email is already associated to a registered user."
msgstr "此电子邮件已经与现有注册用户关联。"
+14 -14
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "3.2.1"
version = "3.3.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -26,14 +26,14 @@ readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"beautifulsoup4==4.13.4",
"boto3==1.38.18",
"boto3==1.38.27",
"Brotli==1.1.0",
"celery[redis]==5.5.2",
"celery[redis]==5.5.3",
"django-configurations==2.5.1",
"django-cors-headers==4.7.0",
"django-countries==7.6.1",
"django-filter==25.1",
"django-lasuite[all]==0.0.8",
"django-lasuite[all]==0.0.9",
"django-parler==2.3",
"django-redis==5.4.0",
"django-storages[s3]==1.14.6",
@@ -46,19 +46,19 @@ dependencies = [
"easy_thumbnails==2.10",
"factory_boy==3.3.3",
"gunicorn==23.0.0",
"jsonschema==4.23.0",
"jsonschema==4.24.0",
"lxml==5.4.0",
"markdown==3.8",
"mozilla-django-oidc==4.0.1",
"nested-multipart-parser==1.5.0",
"openai==1.79.0",
"openai==1.82.1",
"psycopg[binary]==3.2.9",
"pycrdt==0.12.19",
"pycrdt==0.12.20",
"PyJWT==2.10.1",
"python-magic==0.4.27",
"redis<6.0.0",
"requests==2.32.3",
"sentry-sdk==2.28.0",
"sentry-sdk==2.29.1",
"whitenoise==6.9.0",
]
@@ -72,10 +72,10 @@ dependencies = [
dev = [
"django-extensions==4.1",
"django-test-migrations==1.5.0",
"drf-spectacular-sidecar==2025.5.1",
"freezegun==1.5.1",
"drf-spectacular-sidecar==2025.6.1",
"freezegun==1.5.2",
"ipdb==0.13.13",
"ipython==9.2.0",
"ipython==9.3.0",
"pyfakefs==5.8.0",
"pylint-django==2.6.1",
"pylint==3.3.7",
@@ -83,10 +83,10 @@ dev = [
"pytest-django==4.11.1",
"pytest==8.3.5",
"pytest-icdiff==0.9",
"pytest-xdist==3.6.1",
"pytest-xdist==3.7.0",
"responses==0.25.7",
"ruff==0.11.10",
"types-requests==2.32.0.20250515",
"ruff==0.11.12",
"types-requests==2.32.0.20250602",
]
[tool.setuptools]
+1 -13
View File
@@ -50,19 +50,7 @@ ENV NEXT_PUBLIC_PUBLISH_AS_MIT=${PUBLISH_AS_MIT}
RUN yarn build
# ---- Front-end image ----
FROM nginxinc/nginx-unprivileged:1.27-alpine AS frontend-production
# Remove the upgrade part once nginx has published
# a new image that fixes the CVE related to libxml2
ARG UID=101
USER root
# Upgrade system packages to install security updates
RUN apk update && \
apk upgrade && \
rm -rf /var/cache/apk/*
USER $UID
FROM nginxinc/nginx-unprivileged:alpine3.21 AS frontend-production
# Un-privileged user running the application
ARG DOCKER_USER
@@ -10,8 +10,6 @@ test.beforeEach(async ({ page }) => {
test.describe('404', () => {
test('Checks all the elements are visible', async ({ page }) => {
await expect(page.getByLabel('Image 404')).toBeVisible();
await expect(page.getByText('Ouch')).toBeVisible();
await expect(
page.getByText(
'It seems that the page you are looking for does not exist or cannot be displayed correctly.',
@@ -4,10 +4,11 @@ export const CONFIG = {
AI_FEATURE_ENABLED: true,
CRISP_WEBSITE_ID: null,
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: false,
ENVIRONMENT: 'development',
FRONTEND_CSS_URL: null,
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
FRONTEND_THEME: 'default',
FRONTEND_THEME: null,
MEDIA_BASE_URL: 'http://localhost:8083',
LANGUAGES: [
['en-us', 'English'],
@@ -20,7 +21,25 @@ export const CONFIG = {
POSTHOG_KEY: {},
SENTRY_DSN: null,
theme_customization: {},
};
} as const;
export const overrideConfig = async (
page: Page,
newConfig: { [K in keyof typeof CONFIG]?: unknown },
) =>
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...CONFIG,
...newConfig,
},
});
} else {
await route.continue();
}
});
export const keyCloakSignIn = async (
page: Page,
@@ -28,10 +47,7 @@ export const keyCloakSignIn = async (
fromHome: boolean = true,
) => {
if (fromHome) {
await page
.getByRole('button', { name: 'Proconnect Login' })
.first()
.click();
await page.getByRole('button', { name: 'Start Writing' }).first().click();
}
const login = `user-e2e-${browserName}`;
@@ -91,9 +107,16 @@ export const createDoc = async (
};
export const verifyDocName = async (page: Page, docName: string) => {
const input = page.getByRole('textbox', { name: 'doc title input' });
await expect(
page.getByLabel('It is the card information about the document.'),
).toBeVisible({
timeout: 10000,
});
try {
await expect(input).toHaveText(docName);
await expect(
page.getByRole('textbox', { name: 'doc title input' }),
).toHaveText(docName);
} catch {
await expect(page.getByRole('heading', { name: docName })).toBeVisible();
}
@@ -2,38 +2,14 @@ import path from 'path';
import { expect, test } from '@playwright/test';
import { CONFIG, createDoc } from './common';
import { CONFIG, createDoc, overrideConfig } from './common';
test.describe('Config', () => {
test('it checks the config api is called', async ({ page }) => {
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes('/config/') && response.status() === 200,
);
await page.goto('/');
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
expect(await response.json()).toStrictEqual(CONFIG);
});
test('it checks that sentry is trying to init from config endpoint', async ({
page,
}) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...CONFIG,
SENTRY_DSN: 'https://sentry.io/123',
},
});
} else {
await route.continue();
}
await overrideConfig(page, {
SENTRY_DSN: 'https://sentry.io/123',
});
const invalidMsg = 'Invalid Sentry Dsn: https://sentry.io/123';
@@ -98,18 +74,8 @@ test.describe('Config', () => {
page,
browserName,
}) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...CONFIG,
AI_FEATURE_ENABLED: false,
},
});
} else {
await route.continue();
}
await overrideConfig(page, {
AI_FEATURE_ENABLED: false,
});
await page.goto('/');
@@ -129,18 +95,8 @@ test.describe('Config', () => {
test('it checks that Crisp is trying to init from config endpoint', async ({
page,
}) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...CONFIG,
CRISP_WEBSITE_ID: '1234',
},
});
} else {
await route.continue();
}
await overrideConfig(page, {
CRISP_WEBSITE_ID: '1234',
});
await page.goto('/');
@@ -151,18 +107,8 @@ test.describe('Config', () => {
});
test('it checks FRONTEND_CSS_URL config', async ({ page }) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...CONFIG,
FRONTEND_CSS_URL: 'http://localhost:123465/css/style.css',
},
});
} else {
await route.continue();
}
await overrideConfig(page, {
FRONTEND_CSS_URL: 'http://localhost:123465/css/style.css',
});
await page.goto('/');
@@ -178,9 +124,7 @@ test.describe('Config', () => {
test.describe('Config: Not loggued', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('it checks that theme is configured from config endpoint', async ({
page,
}) => {
test('it checks the config api is called', async ({ page }) => {
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes('/config/') && response.status() === 200,
@@ -191,11 +135,25 @@ test.describe('Config: Not loggued', () => {
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
const jsonResponse = await response.json();
expect(jsonResponse.FRONTEND_THEME).toStrictEqual('default');
const json = (await response.json()) as typeof CONFIG;
const { theme_customization, ...configApi } = json;
expect(theme_customization).toBeDefined();
const { theme_customization: _, ...CONFIG_LEFT } = CONFIG;
const footer = page.locator('footer').first();
expect(configApi).toStrictEqual(CONFIG_LEFT);
});
test('it checks that theme is configured from config endpoint', async ({
page,
}) => {
await overrideConfig(page, {
FRONTEND_THEME: 'dsfr',
});
await page.goto('/');
const header = page.locator('header').first();
// alt 'Gouvernement Logo' comes from the theme
await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible();
await expect(header.getByAltText('Gouvernement Logo')).toBeVisible();
});
});
@@ -425,6 +425,10 @@ test.describe('Doc Editor', () => {
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`html`);
});
const responseCheckPromise = page.waitForResponse(
(response) =>
response.url().includes('media-check') && response.status() === 200,
);
await verifyDocName(page, randomDoc);
@@ -439,6 +443,8 @@ test.describe('Doc Editor', () => {
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, 'assets/test.html'));
await responseCheckPromise;
await page.locator('.bn-block-content[data-name="test.html"]').click();
await page.getByRole('button', { name: 'Download file' }).click();
@@ -455,6 +461,51 @@ test.describe('Doc Editor', () => {
expect(svgBuffer.toString()).toContain('Hello svg');
});
test('it analyzes uploads', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
let requestCount = 0;
await page.route(
/.*\/documents\/.*\/media-check\/\?key=.*/,
async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
status: requestCount ? 'ready' : 'processing',
file: '/anything.html',
},
});
requestCount++;
} else {
await route.continue();
}
},
);
const fileChooserPromise = page.waitForEvent('filechooser');
await verifyDocName(page, randomDoc);
const editor = page.locator('.ProseMirror.bn-editor');
await editor.click();
await editor.locator('.bn-block-outer').last().fill('/');
await page.getByText('Embedded file').click();
await page.getByText('Upload file').click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(path.join(__dirname, 'assets/test.html'));
await expect(editor.getByText('Analyzing file...')).toBeVisible();
// The retry takes a few seconds
await expect(editor.getByText('test.html')).toBeVisible({
timeout: 7000,
});
await expect(editor.getByText('Analyzing file...')).toBeHidden();
});
test('it checks block editing when not connected to collab server', async ({
page,
}) => {
@@ -465,6 +516,7 @@ test.describe('Doc Editor', () => {
json: {
...CONFIG,
COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
},
});
} else {
@@ -208,7 +208,6 @@ test.describe('Documents filters', () => {
// Initial state
await expect(allDocs).toBeVisible();
await expect(allDocs).toHaveCSS('background-color', 'rgb(238, 238, 238)');
await expect(allDocs).toHaveAttribute('aria-selected', 'true');
await expect(myDocs).toBeVisible();
@@ -101,6 +101,8 @@ test.describe('Doc Visibility: Restricted', () => {
page,
browserName,
}) => {
test.slow();
await page.goto('/');
await keyCloakSignIn(page, browserName);
@@ -121,14 +123,16 @@ test.describe('Doc Visibility: Restricted', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await page.goto(urlDoc);
await expect(
page.getByText('You do not have permission to view this document.'),
).toBeVisible();
).toBeVisible({
timeout: 10000,
});
});
test('A doc is accessible when member.', async ({ page, browserName }) => {
@@ -173,7 +177,7 @@ test.describe('Doc Visibility: Restricted', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await page.goto(urlDoc);
@@ -430,7 +434,7 @@ test.describe('Doc Visibility: Authenticated', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await page.goto(urlDoc);
@@ -490,7 +494,7 @@ test.describe('Doc Visibility: Authenticated', () => {
await keyCloakSignIn(page, otherBrowser!);
await expect(
page.getByRole('link', { name: 'Docs Logo Docs BETA' }),
page.getByRole('link', { name: 'Docs Logo Docs' }),
).toBeVisible();
await page.goto(urlDoc);
@@ -1,14 +1,115 @@
import { expect, test } from '@playwright/test';
import { overrideConfig } from './common';
test.describe('Footer', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('checks the footer is not displayed if no config', async ({ page }) => {
await overrideConfig(page, {
theme_customization: {},
});
await page.goto('/');
await expect(page.locator('footer')).toBeHidden();
});
test('checks all the elements are visible', async ({ page }) => {
await page.goto('/');
const footer = page.locator('footer').first();
await expect(footer.getByAltText('Docs Logo')).toBeVisible();
await expect(footer.getByRole('heading', { name: 'Docs' })).toBeVisible();
await expect(footer.getByRole('link', { name: 'Github' })).toBeVisible();
await expect(footer.getByRole('link', { name: 'DINUM' })).toBeVisible();
await expect(footer.getByRole('link', { name: 'ZenDiS' })).toBeVisible();
await expect(
footer.getByRole('link', { name: 'BlockNote.js' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Legal Notice' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Personal data and cookies' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Accessibility' }),
).toBeVisible();
await expect(
footer.getByText(
'Unless otherwise stated, all content on this site is under licence',
),
).toBeVisible();
// Check the translation
const header = page.locator('header').first();
await header.getByRole('button').getByText('English').click();
await page.getByLabel('Français').click();
await expect(
page.locator('footer').getByText('Mentions légales'),
).toBeVisible();
});
test('checks the footer is correctly overrided', async ({ page }) => {
await overrideConfig(page, {
FRONTEND_THEME: 'dsfr',
theme_customization: {
footer: {
default: {
logo: {
src: '/assets/logo-gouv.svg',
width: '220px',
alt: 'Gouvernement Logo',
},
externalLinks: [
{
label: 'legifrance.gouv.fr',
href: '#',
},
{
label: 'info.gouv.fr',
href: '#',
},
],
legalLinks: [
{
label: 'Legal link',
href: '#',
},
],
bottomInformation: {
label: 'Some bottom information text',
link: {
label: 'a custom label',
href: '#',
},
},
},
fr: {
bottomInformation: {
label: "Text d'information en bas de page en français",
link: {
label: 'un label personnalisé',
href: '#',
},
},
},
},
},
});
await page.goto('/');
const footer = page.locator('footer').first();
await expect(footer.getByAltText('Gouvernement Logo')).toBeVisible();
await expect(footer.getByRole('heading', { name: 'Docs' })).toBeHidden();
await expect(footer.getByText('BETA')).toBeHidden();
await expect(
footer.getByRole('link', { name: 'legifrance.gouv.fr' }),
).toBeVisible();
@@ -18,53 +119,30 @@ test.describe('Footer', () => {
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'service-public.fr' }),
footer.getByRole('link', { name: 'Legal link' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'data.gouv.fr' }),
footer.getByText('Some bottom information text'),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Legal Notice' }),
footer.getByRole('link', { name: 'a custom label' }),
).toBeVisible();
// Check the translation
const header = page.locator('header').first();
await header.getByRole('button').getByText('English').click();
await page.getByLabel('Français').click();
await expect(
page
.locator('footer')
.getByText("Text d'information en bas de page en français"),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Personal data and cookies' }),
).toBeVisible();
await expect(
footer.getByRole('link', { name: 'Accessibility' }),
).toBeVisible();
await expect(
footer.getByText(
'Unless otherwise stated, all content on this site is under licence',
),
footer.getByRole('link', { name: 'un label personnalisé' }),
).toBeVisible();
});
const legalPages = [
{ name: 'Legal Notice', url: '/legal-notice/' },
{ name: 'Personal data and cookies', url: '/personal-data-cookies/' },
{ name: 'Accessibility', url: '/accessibility/' },
];
for (const { name, url } of legalPages) {
test(`checks ${name} page`, async ({ page }) => {
await page.goto('/');
const footer = page.locator('footer').first();
await footer.getByRole('link', { name }).click();
await expect(
page
.getByRole('heading', {
name,
})
.first(),
).toBeVisible();
await expect(page).toHaveURL(url);
});
}
});
@@ -1,20 +1,39 @@
import { expect, test } from '@playwright/test';
import { expectLoginPage, keyCloakSignIn } from './common';
import { expectLoginPage, keyCloakSignIn, overrideConfig } from './common';
test.describe('Header', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('checks all the elements are visible', async ({ page }) => {
await page.goto('/');
const header = page.locator('header').first();
await expect(header.getByLabel('Docs Logo')).toBeVisible();
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
'color',
'rgb(0, 0, 145)',
'font-family',
/Roboto/i,
);
await expect(
header.getByRole('button', {
name: 'Logout',
}),
).toBeVisible();
await expect(header.getByText('English')).toBeVisible();
});
test('checks all the elements are visible with DSFR theme', async ({
page,
}) => {
await overrideConfig(page, {
FRONTEND_THEME: 'dsfr',
});
await page.goto('/');
const header = page.locator('header').first();
await expect(header.getByLabel('Docs Logo')).toBeVisible();
await expect(header.locator('h2').getByText('Docs')).toHaveCSS(
'font-family',
/Marianne/i,
@@ -36,6 +55,11 @@ test.describe('Header', () => {
});
test('checks La Gauffre interaction', async ({ page }) => {
await overrideConfig(page, {
FRONTEND_THEME: 'dsfr',
});
await page.goto('/');
const header = page.locator('header').first();
await expect(
@@ -68,11 +92,13 @@ test.describe('Header', () => {
test.describe('Header mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('it checks the header when mobile with DSFR theme', async ({ page }) => {
await overrideConfig(page, {
FRONTEND_THEME: 'dsfr',
});
await page.goto('/');
test('it checks the header when mobile', async ({ page }) => {
const header = page.locator('header').first();
await expect(header.getByLabel('Open the header menu')).toBeVisible();
@@ -1,6 +1,6 @@
import { expect, test } from '@playwright/test';
import { CONFIG } from './common';
import { overrideConfig } from './common';
test.beforeEach(async ({ page }) => {
await page.goto('/docs/');
@@ -9,6 +9,62 @@ test.beforeEach(async ({ page }) => {
test.describe('Home page', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('checks all the elements are visible', async ({ page }) => {
await page.goto('/docs/');
// Check header content
const header = page.locator('header').first();
const footer = page.locator('footer').first();
await expect(header).toBeVisible();
await expect(
header.getByRole('button', { name: /Language/ }),
).toBeVisible();
await expect(header.getByRole('img', { name: 'Docs logo' })).toBeVisible();
await expect(header.getByRole('heading', { name: 'Docs' })).toBeVisible();
// Check the titles
const h2 = page.locator('h2');
await expect(h2.getByText('Govs ❤️ Open Source.')).toBeVisible();
await expect(
h2.getByText('Collaborative writing, Simplified.'),
).toBeVisible();
await expect(
h2.getByText('An uncompromising writing experience.'),
).toBeVisible();
await expect(
h2.getByText('Simple and secure collaboration.'),
).toBeVisible();
await expect(h2.getByText('Flexible export.')).toBeVisible();
await expect(
h2.getByText('A new way to organize knowledge.'),
).toBeVisible();
await expect(
page.getByRole('button', { name: 'Start Writing' }),
).toBeVisible();
await expect(footer).toBeVisible();
});
test('checks all the elements are visible with dsfr theme', async ({
page,
}) => {
await overrideConfig(page, {
FRONTEND_THEME: 'dsfr',
theme_customization: {
footer: {
default: {
externalLinks: [
{
label: 'legifrance.gouv.fr',
href: '#',
},
],
},
},
},
});
await page.goto('/docs/');
// Check header content
const header = page.locator('header').first();
const footer = page.locator('footer').first();
@@ -54,18 +110,8 @@ test.describe('Home page', () => {
});
test('it checks the homepage feature flag', async ({ page }) => {
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
if (request.method().includes('GET')) {
await route.fulfill({
json: {
...CONFIG,
FRONTEND_HOMEPAGE_FEATURE_ENABLED: false,
},
});
} else {
await route.continue();
}
await overrideConfig(page, {
FRONTEND_HOMEPAGE_FEATURE_ENABLED: false,
});
await page.goto('/');
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "app-e2e",
"version": "3.2.1",
"version": "3.3.0",
"private": true,
"scripts": {
"lint": "eslint . --ext .ts",
+204 -15
View File
@@ -3,6 +3,8 @@ import { cunninghamConfig as tokens } from '@gouvfr-lasuite/ui-kit';
const customColors = {
'primary-action': '#1212FF',
'primary-bg': '#FAFAFA',
'primary-focus': '#0A76F6',
'secondary-icon': 'var(--c--theme--colors--primary-text)',
'blue-400': '#7AB1E8',
'blue-500': '#417DC4',
'blue-600': '#3558A2',
@@ -35,19 +37,18 @@ const customColors = {
'yellow-600': '#66673D',
};
tokens.themes.default.theme.colors = {
...tokens.themes.default.theme.colors,
...customColors,
};
tokens.themes.default.theme = {
...tokens.themes.default.theme,
...{
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
src: '',
alt: '',
widthHeader: '',
widthFooter: '',
},
colors: {
...tokens.themes.default.theme.colors,
...customColors,
},
},
};
@@ -55,13 +56,201 @@ tokens.themes.default.theme = {
tokens.themes.default.components = {
...tokens.themes.default.components,
...{
'la-gauffre': {
activated: true,
},
'home-proconnect': {
activated: true,
'la-gaufre': false,
'home-proconnect': false,
beta: false,
'image-system-filter': '',
favicon: {
ico: '/assets/favicon-light.ico',
'png-light': '/assets/favicon-light.png',
'png-dark': '/assets/favicon-dark.png',
},
},
};
export default tokens;
const dsfrTheme = {
dsfr: {
theme: {
colors: {
'secondary-icon': '#C9191E',
},
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
},
},
components: {
'la-gaufre': true,
'home-proconnect': true,
beta: true,
favicon: {
ico: '/assets/favicon-dsfr.ico',
'png-light': '/assets/favicon-dsfr.png',
'png-dark': '/assets/favicon-dark-dsfr.png',
},
},
},
};
const genericTheme = {
generic: {
theme: {
colors: {
'primary-action': '#206EBD',
'primary-focus': '#1E64BF',
'primary-text': '#2E2C28',
'primary-050': '#F8F8F7',
'primary-100': '#F0EFEC',
'primary-150': '#F4F4FD',
'primary-200': '#E8E7E4',
'primary-300': '#CFCDC9',
'primary-400': '#979592',
'primary-500': '#82807D',
'primary-600': '#3F3D39',
'primary-700': '#2E2C28',
'primary-800': '#302E29',
'primary-900': '#282622',
'primary-950': '#201F1C',
'secondary-text': '#fff',
'secondary-50': '#F4F7FA',
'secondary-100': '#D7E3EE',
'secondary-200': '#B8CCE1',
'secondary-300': '#99B4D3',
'secondary-400': '#7595BE',
'secondary-500': '#5874A0',
'secondary-600': '#3A5383',
'secondary-700': '#1E3462',
'secondary-800': '#091B41',
'secondary-900': '#08183B',
'secondary-950': '#071636',
'greyscale-text': '#3C3B38',
'greyscale-000': '#fff',
'greyscale-050': '#F8F7F7',
'greyscale-100': '#F3F3F2',
'greyscale-200': '#ECEBEA',
'greyscale-250': '#E4E3E2',
'greyscale-300': '#D3D2CF',
'greyscale-350': '#eee',
'greyscale-400': '#96948E',
'greyscale-500': '#817E77',
'greyscale-600': '#6A6862',
'greyscale-700': '#3C3B38',
'greyscale-750': '#383632',
'greyscale-800': '#2D2B27',
'greyscale-900': '#262522',
'greyscale-950': '#201F1C',
'greyscale-1000': '#181714',
'success-text': '#234935',
'success-50': '#F3FBF5',
'success-100': '#E4F7EA',
'success-200': '#CAEED4',
'success-300': '#A0E0B5',
'success-400': '#6CC88C',
'success-500': '#6CC88C',
'success-600': '#358D5C',
'success-700': '#2D704B',
'success-800': '#28583F',
'success-900': '#234935',
'success-950': '#0F281B',
'info-text': '#212445',
'info-50': '#F2F6FB',
'info-100': '#E2E9F5',
'info-200': '#CCD8EE',
'info-300': '#A9C0E3',
'info-400': '#809DD4',
'info-500': '#617BC7',
'info-600': '#4A5CBF',
'info-700': '#3E49B2',
'info-800': '#353C8F',
'info-900': '#303771',
'info-950': '#212445',
'warning-text': '#D97C3A',
'warning-50': '#FDF7F1',
'warning-100': '#FBEDDC',
'warning-200': '#F5D9B9',
'warning-300': '#EDBE8C',
'warning-400': '#E2985C',
'warning-500': '#D97C3A',
'warning-600': '#C96330',
'warning-700': '#A34B32',
'warning-800': '#813B2C',
'warning-900': '#693327',
'warning-950': '#381713',
'danger-action': '#C0182A',
'danger-text': '#FFF',
'danger-050': '#FDF5F4',
'danger-100': '#FBEBE8',
'danger-200': '#F9E0DC',
'danger-300': '#F3C3BD',
'danger-400': '#E26552',
'danger-500': '#C91F00',
'danger-600': '#A71901',
'danger-700': '#562C2B',
'danger-800': '#392425',
'danger-900': '#311F20',
'danger-950': '#2A191A',
'blue-400': '#8BAECC',
'blue-500': '#567AA2',
'blue-600': '#455784',
'brown-400': '#E4C090',
'brown-500': '#BA9977',
'brown-600': '#735C45',
'cyan-400': '#5CBEC9',
'cyan-500': '#43A1B3',
'cyan-600': '#39809B',
'gold-400': '#ECBF50',
'gold-500': '#DFA038',
'gold-600': '#C17B31',
'green-400': '#5DBD9A',
'green-500': '#3AA183',
'green-600': '#2A816D',
'olive-400': '#AFD662',
'olive-500': '#90BB4B',
'olive-600': '#6E9441',
'orange-400': '#E2985C',
'orange-500': '#D97C3A',
'orange-600': '#C96330',
'pink-400': '#BE8FC8',
'pink-500': '#A563B1',
'pink-600': '#8B44A5',
'purple-400': '#BE8FC8',
'purple-500': '#A563B1',
'purple-600': '#8B44A5',
'yellow-400': '#EDC947',
'yellow-500': '#DBB13A',
'yellow-600': '#B88A34',
},
font: {
families: {
base: 'Inter, Roboto Flex Variable, sans-serif',
accent: 'Inter, Roboto Flex Variable, sans-serif',
},
},
},
components: {
button: {
primary: {
background: {
'color-hover': 'var(--c--theme--colors--primary-focus)',
'color-active': 'var(--c--theme--colors--primary-focus)',
'color-focus': 'var(--c--theme--colors--primary-focus)',
},
},
},
'image-system-filter': 'saturate(0.2)',
},
},
};
const docsTokens = {
...tokens,
themes: {
...tokens.themes,
...dsfrTheme,
...genericTheme,
},
};
export default docsTokens;
+20 -19
View File
@@ -1,12 +1,12 @@
{
"name": "app-impress",
"version": "3.2.1",
"version": "3.3.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "prettier --check . && yarn stylelint && next build",
"build:ci": "cp .env.development .env.local && yarn build",
"build-theme": "cunningham -g css,ts -o src/cunningham --utility-classes && yarn prettier",
"build-theme": "cunningham -g css,ts -o src/cunningham --utility-classes && yarn prettier && yarn stylelint --fix",
"start": "npx -y serve@latest out",
"lint": "tsc --noEmit && next lint",
"prettier": "prettier --write .",
@@ -16,56 +16,57 @@
},
"dependencies": {
"@ag-media/react-pdf-table": "2.0.3",
"@blocknote/code-block": "0.30.0",
"@blocknote/core": "0.30.0",
"@blocknote/mantine": "0.30.0",
"@blocknote/react": "0.30.0",
"@blocknote/xl-docx-exporter": "0.30.0",
"@blocknote/xl-pdf-exporter": "0.30.0",
"@blocknote/code-block": "0.31.1",
"@blocknote/core": "0.31.1",
"@blocknote/mantine": "0.31.1",
"@blocknote/react": "0.31.1",
"@blocknote/xl-docx-exporter": "0.31.1",
"@blocknote/xl-pdf-exporter": "0.31.1",
"@emoji-mart/data": "1.2.1",
"@emoji-mart/react": "1.1.1",
"@fontsource/material-icons": "5.2.5",
"@gouvfr-lasuite/integration": "1.0.3",
"@gouvfr-lasuite/ui-kit": "0.6.0",
"@gouvfr-lasuite/ui-kit": "0.7.0",
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "3.1.0",
"@react-pdf/renderer": "4.3.0",
"@sentry/nextjs": "9.19.0",
"@tanstack/react-query": "5.76.1",
"@sentry/nextjs": "9.22.0",
"@tanstack/react-query": "5.77.1",
"canvg": "4.0.3",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"crisp-sdk-web": "1.0.25",
"docx": "9.5.0",
"emoji-mart": "5.6.0",
"i18next": "25.1.3",
"i18next": "25.2.1",
"i18next-browser-languagedetector": "8.1.0",
"idb": "8.0.3",
"lodash": "4.17.21",
"luxon": "3.6.1",
"next": "15.3.2",
"posthog-js": "1.242.2",
"posthog-js": "1.246.0",
"react": "*",
"react-aria-components": "1.8.0",
"react-aria-components": "1.9.0",
"react-dom": "*",
"react-i18next": "15.5.1",
"react-i18next": "15.5.2",
"react-intersection-observer": "9.16.0",
"react-select": "5.10.1",
"styled-components": "6.1.18",
"tldraw": "3.13.1",
"use-debounce": "10.0.4",
"y-protocols": "1.0.6",
"yjs": "*",
"zustand": "5.0.4"
"zustand": "5.0.5"
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.76.1",
"@tanstack/react-query-devtools": "5.77.1",
"@testing-library/dom": "10.4.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/react": "16.3.0",
"@testing-library/user-event": "14.6.1",
"@types/jest": "29.5.14",
"@types/lodash": "4.17.16",
"@types/lodash": "4.17.17",
"@types/luxon": "3.6.2",
"@types/node": "*",
"@types/react": "*",
@@ -82,7 +83,7 @@
"stylelint-config-standard": "38.0.0",
"stylelint-prettier": "5.0.3",
"typescript": "*",
"webpack": "5.99.8",
"webpack": "5.99.9",
"workbox-webpack-plugin": "7.1.0"
}
}

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before

Width:  |  Height:  |  Size: 992 B

After

Width:  |  Height:  |  Size: 992 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,12 @@
<svg viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21.6305 29.5812C22.7983 29.2538 23.9166 28.6562 24.6505 27.6003C25.3749 26.5663 25.5789 25.2547 25.5789 23.9925V5.50099C25.5789 5.17358 25.5611 4.84557 25.5216 4.52148C26.1016 4.74961 26.5486 5.12658 26.8626 5.65239C27.2331 6.25024 27.4184 7.03757 27.4184 8.01435V26.7964C27.4184 28.1184 27.0942 29.1078 26.4458 29.7646C25.7974 30.4214 24.8207 30.7498 23.5155 30.7498H16.4209C16.5889 30.7204 16.7574 30.6901 16.9262 30.659C18.4067 30.3944 19.9713 30.0354 21.6185 29.5846L21.6305 29.5812Z"
fill="#C9191E"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.58203 26.405V7.5977C4.58203 6.45251 4.88938 5.58519 5.50408 4.99575C6.1272 4.40631 6.95242 4.08212 7.97972 4.02318C9.49542 3.93055 10.9311 3.80425 12.2868 3.64425C13.6425 3.47584 14.9393 3.28217 16.1771 3.06324C17.4234 2.8443 18.6359 2.60011 19.8148 2.33065C21.0274 2.04435 21.9578 2.1875 22.6062 2.7601C23.2546 3.33269 23.5788 4.24632 23.5788 5.50099V23.9925C23.5788 25.0956 23.3893 25.9166 23.0104 26.4555C22.6315 27.0029 21.9915 27.4028 21.0905 27.6554C19.4906 28.0933 17.9833 28.4386 16.5687 28.6912C15.154 28.9522 13.7731 29.1501 12.4258 29.2848C11.0785 29.4196 9.69751 29.5248 8.28286 29.6006C7.11241 29.668 6.20299 29.4238 5.5546 28.868C4.90622 28.3207 4.58203 27.4997 4.58203 26.405ZM9.20865 11.0124C11.0635 10.8944 12.7632 10.7131 14.3075 10.4683C14.6822 10.4072 15.0564 10.3436 15.4291 10.2776C15.8192 10.2085 16.1013 9.86859 16.1013 9.47337C16.1013 8.96154 15.638 8.57609 15.135 8.66189C14.846 8.71118 14.5555 8.75909 14.2635 8.80562C12.7346 9.04923 11.0452 9.22998 9.19523 9.3477C8.91819 9.36558 8.69776 9.45188 8.55608 9.62391C8.42209 9.78661 8.35645 9.98229 8.35645 10.2053C8.35645 10.4321 8.43296 10.6295 8.58568 10.7918L8.58783 10.7939C8.75336 10.9595 8.96369 11.0311 9.20865 11.0124ZM9.20801 15.206C11.0631 15.088 12.763 14.9066 14.3075 14.6619C15.8588 14.4089 17.3936 14.1138 18.9112 13.7766C19.2191 13.7081 19.4498 13.6003 19.5652 13.433C19.6786 13.2721 19.7347 13.0876 19.7347 12.8832C19.7347 12.6526 19.6469 12.454 19.476 12.2926C19.2921 12.1189 19.0348 12.0784 18.7304 12.1411L18.7285 12.1415C17.2823 12.4694 15.794 12.7553 14.2635 12.9992C12.7346 13.2428 11.0452 13.4235 9.19523 13.5413C8.91819 13.5591 8.69776 13.6454 8.55608 13.8175C8.42276 13.9794 8.35645 14.1705 8.35645 14.3863C8.35645 14.6203 8.43209 14.8223 8.58558 14.9854L8.59 14.9896C8.75499 15.1449 8.96316 15.2155 9.20551 15.2062L9.20801 15.206ZM9.20847 19.3994C11.0634 19.2729 12.7631 19.0874 14.3075 18.8427C15.8589 18.5982 17.3934 18.3073 18.9112 17.97C19.2199 17.9014 19.4508 17.7891 19.566 17.6127C19.6783 17.4529 19.7347 17.2733 19.7347 17.0766C19.7347 16.8461 19.6469 16.6474 19.476 16.4861C19.2921 16.3123 19.0348 16.2718 18.7304 16.3345L18.729 16.3348C17.2827 16.6543 15.7942 16.9361 14.2635 17.18C12.7345 17.4236 11.045 17.6086 9.19495 17.7347C8.91804 17.7526 8.69771 17.8389 8.55608 18.0109C8.42276 18.1728 8.35645 18.3639 8.35645 18.5797C8.35645 18.8137 8.43209 19.0158 8.58558 19.1789L8.59 19.183C8.75499 19.3383 8.96316 19.4089 9.20551 19.3996L9.20847 19.3994ZM14.3075 23.007C12.7632 23.2518 11.0635 23.4331 9.20867 23.5512C8.9637 23.5698 8.75337 23.4982 8.58783 23.3326L8.58572 23.3305C8.433 23.1682 8.35645 22.9708 8.35645 22.7441C8.35645 22.521 8.42209 22.3253 8.55608 22.1626C8.69776 21.9906 8.91827 21.9043 9.19531 21.8864C11.0453 21.7687 12.7346 21.588 14.2635 21.3443C14.5555 21.2978 14.846 21.2499 15.135 21.2006C15.638 21.1148 16.1013 21.5003 16.1013 22.0121C16.1013 22.4073 15.8192 22.7472 15.4291 22.8163C15.0564 22.8823 14.6822 22.9459 14.3075 23.007Z"
fill="#000091"
/>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -1,12 +1,12 @@
<svg viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21.6305 29.5812C22.7983 29.2538 23.9166 28.6562 24.6505 27.6003C25.3749 26.5663 25.5789 25.2547 25.5789 23.9925V5.50099C25.5789 5.17358 25.5611 4.84557 25.5216 4.52148C26.1016 4.74961 26.5486 5.12658 26.8626 5.65239C27.2331 6.25024 27.4184 7.03757 27.4184 8.01435V26.7964C27.4184 28.1184 27.0942 29.1078 26.4458 29.7646C25.7974 30.4214 24.8207 30.7498 23.5155 30.7498H16.4209C16.5889 30.7204 16.7574 30.6901 16.9262 30.659C18.4067 30.3944 19.9713 30.0354 21.6185 29.5846L21.6305 29.5812Z"
fill="#C9191E"
fill="var(--c--theme--colors--secondary-icon)"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M4.58203 26.405V7.5977C4.58203 6.45251 4.88938 5.58519 5.50408 4.99575C6.1272 4.40631 6.95242 4.08212 7.97972 4.02318C9.49542 3.93055 10.9311 3.80425 12.2868 3.64425C13.6425 3.47584 14.9393 3.28217 16.1771 3.06324C17.4234 2.8443 18.6359 2.60011 19.8148 2.33065C21.0274 2.04435 21.9578 2.1875 22.6062 2.7601C23.2546 3.33269 23.5788 4.24632 23.5788 5.50099V23.9925C23.5788 25.0956 23.3893 25.9166 23.0104 26.4555C22.6315 27.0029 21.9915 27.4028 21.0905 27.6554C19.4906 28.0933 17.9833 28.4386 16.5687 28.6912C15.154 28.9522 13.7731 29.1501 12.4258 29.2848C11.0785 29.4196 9.69751 29.5248 8.28286 29.6006C7.11241 29.668 6.20299 29.4238 5.5546 28.868C4.90622 28.3207 4.58203 27.4997 4.58203 26.405ZM9.20865 11.0124C11.0635 10.8944 12.7632 10.7131 14.3075 10.4683C14.6822 10.4072 15.0564 10.3436 15.4291 10.2776C15.8192 10.2085 16.1013 9.86859 16.1013 9.47337C16.1013 8.96154 15.638 8.57609 15.135 8.66189C14.846 8.71118 14.5555 8.75909 14.2635 8.80562C12.7346 9.04923 11.0452 9.22998 9.19523 9.3477C8.91819 9.36558 8.69776 9.45188 8.55608 9.62391C8.42209 9.78661 8.35645 9.98229 8.35645 10.2053C8.35645 10.4321 8.43296 10.6295 8.58568 10.7918L8.58783 10.7939C8.75336 10.9595 8.96369 11.0311 9.20865 11.0124ZM9.20801 15.206C11.0631 15.088 12.763 14.9066 14.3075 14.6619C15.8588 14.4089 17.3936 14.1138 18.9112 13.7766C19.2191 13.7081 19.4498 13.6003 19.5652 13.433C19.6786 13.2721 19.7347 13.0876 19.7347 12.8832C19.7347 12.6526 19.6469 12.454 19.476 12.2926C19.2921 12.1189 19.0348 12.0784 18.7304 12.1411L18.7285 12.1415C17.2823 12.4694 15.794 12.7553 14.2635 12.9992C12.7346 13.2428 11.0452 13.4235 9.19523 13.5413C8.91819 13.5591 8.69776 13.6454 8.55608 13.8175C8.42276 13.9794 8.35645 14.1705 8.35645 14.3863C8.35645 14.6203 8.43209 14.8223 8.58558 14.9854L8.59 14.9896C8.75499 15.1449 8.96316 15.2155 9.20551 15.2062L9.20801 15.206ZM9.20847 19.3994C11.0634 19.2729 12.7631 19.0874 14.3075 18.8427C15.8589 18.5982 17.3934 18.3073 18.9112 17.97C19.2199 17.9014 19.4508 17.7891 19.566 17.6127C19.6783 17.4529 19.7347 17.2733 19.7347 17.0766C19.7347 16.8461 19.6469 16.6474 19.476 16.4861C19.2921 16.3123 19.0348 16.2718 18.7304 16.3345L18.729 16.3348C17.2827 16.6543 15.7942 16.9361 14.2635 17.18C12.7345 17.4236 11.045 17.6086 9.19495 17.7347C8.91804 17.7526 8.69771 17.8389 8.55608 18.0109C8.42276 18.1728 8.35645 18.3639 8.35645 18.5797C8.35645 18.8137 8.43209 19.0158 8.58558 19.1789L8.59 19.183C8.75499 19.3383 8.96316 19.4089 9.20551 19.3996L9.20847 19.3994ZM14.3075 23.007C12.7632 23.2518 11.0635 23.4331 9.20867 23.5512C8.9637 23.5698 8.75337 23.4982 8.58783 23.3326L8.58572 23.3305C8.433 23.1682 8.35645 22.9708 8.35645 22.7441C8.35645 22.521 8.42209 22.3253 8.55608 22.1626C8.69776 21.9906 8.91827 21.9043 9.19531 21.8864C11.0453 21.7687 12.7346 21.588 14.2635 21.3443C14.5555 21.2978 14.846 21.2499 15.135 21.2006C15.638 21.1148 16.1013 21.5003 16.1013 22.0121C16.1013 22.4073 15.8192 22.7472 15.4291 22.8163C15.0564 22.8823 14.6822 22.9459 14.3075 23.007Z"
fill="#000091"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -6,7 +6,9 @@ import {
useState,
} from 'react';
import { Button, Popover } from 'react-aria-components';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { useCunninghamTheme } from '@/cunningham';
import { BoxProps } from './Box';
@@ -27,11 +29,9 @@ const StyledButton = styled(Button)<StyledButtonProps>`
background: none;
outline: none;
transition: all 0.2s ease-in-out;
font-family: Marianne, Arial, serif;
font-weight: 500;
font-size: 0.938rem;
padding: 0;
text-wrap: nowrap;
${({ $css }) => $css};
`;
@@ -51,6 +51,8 @@ export const DropButton = ({
children,
label,
}: PropsWithChildren<DropButtonProps>) => {
const { themeTokens } = useCunninghamTheme();
const font = themeTokens['font']?.['families']['base'];
const [isLocalOpen, setIsLocalOpen] = useState(isOpen);
const triggerRef = useRef(null);
@@ -70,7 +72,10 @@ export const DropButton = ({
ref={triggerRef}
onPress={() => onOpenChangeHandler(true)}
aria-label={label}
$css={buttonCss}
$css={css`
font-family: ${font};
${buttonCss};
`}
className="--docs--drop-button"
>
{button}
@@ -2,11 +2,17 @@ import { useQuery } from '@tanstack/react-query';
import { APIError, errorCauses, fetchAPI } from '@/api';
import { Theme } from '@/cunningham/';
import { FooterType } from '@/features/footer';
import { PostHogConf } from '@/services';
interface ThemeCustomization {
footer?: FooterType;
}
interface ConfigResponse {
AI_FEATURE_ENABLED?: boolean;
COLLABORATION_WS_URL?: string;
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY?: boolean;
CRISP_WEBSITE_ID?: string;
ENVIRONMENT: string;
FRONTEND_CSS_URL?: string;
@@ -17,6 +23,7 @@ interface ConfigResponse {
MEDIA_BASE_URL?: string;
POSTHOG_KEY?: PostHogConf;
SENTRY_DSN?: string;
theme_customization?: ThemeCustomization;
}
const LOCAL_STORAGE_KEY = 'docs_config';
@@ -2,8 +2,12 @@ import { useCunninghamTheme } from '../useCunninghamTheme';
describe('<useCunninghamTheme />', () => {
it('has the logo correctly set', () => {
const { themeTokens, setTheme } = useCunninghamTheme.getState();
setTheme('default');
expect(useCunninghamTheme.getState().themeTokens.logo?.src).toBe('');
// Change theme
useCunninghamTheme.getState().setTheme('dsfr');
const { themeTokens } = useCunninghamTheme.getState();
const logo = themeTokens.logo;
expect(logo?.src).toBe('/assets/logo-gouv.svg');
expect(logo?.widthHeader).toBe('110px');
@@ -43,3 +43,36 @@
.c__tooltip {
padding: 4px 6px;
}
/**
* Image System
*/
.c__image-system-filter {
filter: var(--c--components--image-system-filter);
}
@font-face {
font-family: Inter;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url('https://fonts.gstatic.com/s/inter/v18/UcCm3FwrK3iLTcvnUwQT9g.woff2')
format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: Inter;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('https://fonts.gstatic.com/s/inter/v18/UcCo3FwrK3iLTcviYwY.woff2')
format('woff2');
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
U+2215, U+FEFF, U+FFFD;
}
@@ -92,6 +92,8 @@
--c--theme--colors--rose-500: #e18b76;
--c--theme--colors--primary-action: #1212ff;
--c--theme--colors--primary-bg: #fafafa;
--c--theme--colors--primary-focus: #0a76f6;
--c--theme--colors--secondary-icon: var(--c--theme--colors--primary-text);
--c--theme--colors--blue-400: #7ab1e8;
--c--theme--colors--blue-600: #3558a2;
--c--theme--colors--brown-400: #e6be92;
@@ -190,10 +192,10 @@
--c--theme--breakpoints--xxs: 320px;
--c--theme--breakpoints--mobile: 768px;
--c--theme--breakpoints--tablet: 1024px;
--c--theme--logo--src: /assets/logo-gouv.svg;
--c--theme--logo--widthheader: 110px;
--c--theme--logo--widthfooter: 220px;
--c--theme--logo--alt: gouvernement logo;
--c--theme--logo--src: ;
--c--theme--logo--alt: ;
--c--theme--logo--widthheader: ;
--c--theme--logo--widthfooter: ;
--c--components--modal--width-small: 342px;
--c--components--tooltip--padding: 4px 8px;
--c--components--tooltip--background-color: var(
@@ -485,8 +487,13 @@
--c--theme--colors--info-100
);
--c--components--badge--info--color: var(--c--theme--colors--info-600);
--c--components--la-gauffre--activated: true;
--c--components--home-proconnect--activated: true;
--c--components--la-gaufre: false;
--c--components--home-proconnect: false;
--c--components--beta: false;
--c--components--image-system-filter: ;
--c--components--favicon--ico: /assets/favicon-light.ico;
--c--components--favicon--png-light: /assets/favicon-light.png;
--c--components--favicon--png-dark: /assets/favicon-dark.png;
}
.cunningham-theme--dark {
@@ -538,6 +545,158 @@
--c--theme--colors--danger-900: #9d6666;
}
.cunningham-theme--dsfr {
--c--theme--colors--secondary-icon: #c9191e;
--c--theme--logo--src: /assets/logo-gouv.svg;
--c--theme--logo--widthHeader: 110px;
--c--theme--logo--widthFooter: 220px;
--c--theme--logo--alt: gouvernement logo;
--c--components--la-gaufre: true;
--c--components--home-proconnect: true;
--c--components--beta: true;
--c--components--favicon--ico: /assets/favicon-dsfr.ico;
--c--components--favicon--png-light: /assets/favicon-dsfr.png;
--c--components--favicon--png-dark: /assets/favicon-dark-dsfr.png;
}
.cunningham-theme--generic {
--c--theme--colors--primary-action: #206ebd;
--c--theme--colors--primary-focus: #1e64bf;
--c--theme--colors--primary-text: #2e2c28;
--c--theme--colors--primary-050: #f8f8f7;
--c--theme--colors--primary-100: #f0efec;
--c--theme--colors--primary-150: #f4f4fd;
--c--theme--colors--primary-200: #e8e7e4;
--c--theme--colors--primary-300: #cfcdc9;
--c--theme--colors--primary-400: #979592;
--c--theme--colors--primary-500: #82807d;
--c--theme--colors--primary-600: #3f3d39;
--c--theme--colors--primary-700: #2e2c28;
--c--theme--colors--primary-800: #302e29;
--c--theme--colors--primary-900: #282622;
--c--theme--colors--primary-950: #201f1c;
--c--theme--colors--secondary-text: #fff;
--c--theme--colors--secondary-50: #f4f7fa;
--c--theme--colors--secondary-100: #d7e3ee;
--c--theme--colors--secondary-200: #b8cce1;
--c--theme--colors--secondary-300: #99b4d3;
--c--theme--colors--secondary-400: #7595be;
--c--theme--colors--secondary-500: #5874a0;
--c--theme--colors--secondary-600: #3a5383;
--c--theme--colors--secondary-700: #1e3462;
--c--theme--colors--secondary-800: #091b41;
--c--theme--colors--secondary-900: #08183b;
--c--theme--colors--secondary-950: #071636;
--c--theme--colors--greyscale-text: #3c3b38;
--c--theme--colors--greyscale-000: #fff;
--c--theme--colors--greyscale-050: #f8f7f7;
--c--theme--colors--greyscale-100: #f3f3f2;
--c--theme--colors--greyscale-200: #ecebea;
--c--theme--colors--greyscale-250: #e4e3e2;
--c--theme--colors--greyscale-300: #d3d2cf;
--c--theme--colors--greyscale-350: #eee;
--c--theme--colors--greyscale-400: #96948e;
--c--theme--colors--greyscale-500: #817e77;
--c--theme--colors--greyscale-600: #6a6862;
--c--theme--colors--greyscale-700: #3c3b38;
--c--theme--colors--greyscale-750: #383632;
--c--theme--colors--greyscale-800: #2d2b27;
--c--theme--colors--greyscale-900: #262522;
--c--theme--colors--greyscale-950: #201f1c;
--c--theme--colors--greyscale-1000: #181714;
--c--theme--colors--success-text: #234935;
--c--theme--colors--success-50: #f3fbf5;
--c--theme--colors--success-100: #e4f7ea;
--c--theme--colors--success-200: #caeed4;
--c--theme--colors--success-300: #a0e0b5;
--c--theme--colors--success-400: #6cc88c;
--c--theme--colors--success-500: #6cc88c;
--c--theme--colors--success-600: #358d5c;
--c--theme--colors--success-700: #2d704b;
--c--theme--colors--success-800: #28583f;
--c--theme--colors--success-900: #234935;
--c--theme--colors--success-950: #0f281b;
--c--theme--colors--info-text: #212445;
--c--theme--colors--info-50: #f2f6fb;
--c--theme--colors--info-100: #e2e9f5;
--c--theme--colors--info-200: #ccd8ee;
--c--theme--colors--info-300: #a9c0e3;
--c--theme--colors--info-400: #809dd4;
--c--theme--colors--info-500: #617bc7;
--c--theme--colors--info-600: #4a5cbf;
--c--theme--colors--info-700: #3e49b2;
--c--theme--colors--info-800: #353c8f;
--c--theme--colors--info-900: #303771;
--c--theme--colors--info-950: #212445;
--c--theme--colors--warning-text: #d97c3a;
--c--theme--colors--warning-50: #fdf7f1;
--c--theme--colors--warning-100: #fbeddc;
--c--theme--colors--warning-200: #f5d9b9;
--c--theme--colors--warning-300: #edbe8c;
--c--theme--colors--warning-400: #e2985c;
--c--theme--colors--warning-500: #d97c3a;
--c--theme--colors--warning-600: #c96330;
--c--theme--colors--warning-700: #a34b32;
--c--theme--colors--warning-800: #813b2c;
--c--theme--colors--warning-900: #693327;
--c--theme--colors--warning-950: #381713;
--c--theme--colors--danger-action: #c0182a;
--c--theme--colors--danger-text: #fff;
--c--theme--colors--danger-050: #fdf5f4;
--c--theme--colors--danger-100: #fbebe8;
--c--theme--colors--danger-200: #f9e0dc;
--c--theme--colors--danger-300: #f3c3bd;
--c--theme--colors--danger-400: #e26552;
--c--theme--colors--danger-500: #c91f00;
--c--theme--colors--danger-600: #a71901;
--c--theme--colors--danger-700: #562c2b;
--c--theme--colors--danger-800: #392425;
--c--theme--colors--danger-900: #311f20;
--c--theme--colors--danger-950: #2a191a;
--c--theme--colors--blue-400: #8baecc;
--c--theme--colors--blue-500: #567aa2;
--c--theme--colors--blue-600: #455784;
--c--theme--colors--brown-400: #e4c090;
--c--theme--colors--brown-500: #ba9977;
--c--theme--colors--brown-600: #735c45;
--c--theme--colors--cyan-400: #5cbec9;
--c--theme--colors--cyan-500: #43a1b3;
--c--theme--colors--cyan-600: #39809b;
--c--theme--colors--gold-400: #ecbf50;
--c--theme--colors--gold-500: #dfa038;
--c--theme--colors--gold-600: #c17b31;
--c--theme--colors--green-400: #5dbd9a;
--c--theme--colors--green-500: #3aa183;
--c--theme--colors--green-600: #2a816d;
--c--theme--colors--olive-400: #afd662;
--c--theme--colors--olive-500: #90bb4b;
--c--theme--colors--olive-600: #6e9441;
--c--theme--colors--orange-400: #e2985c;
--c--theme--colors--orange-500: #d97c3a;
--c--theme--colors--orange-600: #c96330;
--c--theme--colors--pink-400: #be8fc8;
--c--theme--colors--pink-500: #a563b1;
--c--theme--colors--pink-600: #8b44a5;
--c--theme--colors--purple-400: #be8fc8;
--c--theme--colors--purple-500: #a563b1;
--c--theme--colors--purple-600: #8b44a5;
--c--theme--colors--yellow-400: #edc947;
--c--theme--colors--yellow-500: #dbb13a;
--c--theme--colors--yellow-600: #b88a34;
--c--theme--font--families--base: inter, roboto flex variable, sans-serif;
--c--theme--font--families--accent: inter, roboto flex variable, sans-serif;
--c--components--button--primary--background--color-hover: var(
--c--theme--colors--primary-focus
);
--c--components--button--primary--background--color-active: var(
--c--theme--colors--primary-focus
);
--c--components--button--primary--background--color-focus: var(
--c--theme--colors--primary-focus
);
--c--components--image-system-filter: saturate(0.2);
}
.clr-secondary-text {
color: var(--c--theme--colors--secondary-text);
}
@@ -910,6 +1069,14 @@
color: var(--c--theme--colors--primary-bg);
}
.clr-primary-focus {
color: var(--c--theme--colors--primary-focus);
}
.clr-secondary-icon {
color: var(--c--theme--colors--secondary-icon);
}
.clr-blue-400 {
color: var(--c--theme--colors--blue-400);
}
@@ -1366,6 +1533,14 @@
background-color: var(--c--theme--colors--primary-bg);
}
.bg-primary-focus {
background-color: var(--c--theme--colors--primary-focus);
}
.bg-secondary-icon {
background-color: var(--c--theme--colors--secondary-icon);
}
.bg-blue-400 {
background-color: var(--c--theme--colors--blue-400);
}
@@ -96,6 +96,8 @@ export const tokens = {
'rose-500': '#E18B76',
'primary-action': '#1212FF',
'primary-bg': '#FAFAFA',
'primary-focus': '#0A76F6',
'secondary-icon': 'var(--c--theme--colors--primary-text)',
'blue-400': '#7AB1E8',
'blue-600': '#3558A2',
'brown-400': '#E6BE92',
@@ -208,12 +210,7 @@ export const tokens = {
mobile: '768px',
tablet: '1024px',
},
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
},
logo: { src: '', alt: '', widthHeader: '', widthFooter: '' },
},
components: {
modal: { 'width-small': '342px' },
@@ -366,8 +363,15 @@ export const tokens = {
warning: { 'background-color': '#fff4f3', color: '#b34000' },
info: { 'background-color': '#E8EDFF', color: '#0063CB' },
},
'la-gauffre': { activated: true },
'home-proconnect': { activated: true },
'la-gaufre': false,
'home-proconnect': false,
beta: false,
'image-system-filter': '',
favicon: {
ico: '/assets/favicon-light.ico',
'png-light': '/assets/favicon-light.png',
'png-dark': '/assets/favicon-dark.png',
},
},
},
dark: {
@@ -422,5 +426,173 @@ export const tokens = {
},
},
},
dsfr: {
theme: {
colors: { 'secondary-icon': '#C9191E' },
logo: {
src: '/assets/logo-gouv.svg',
widthHeader: '110px',
widthFooter: '220px',
alt: 'Gouvernement Logo',
},
},
components: {
'la-gaufre': true,
'home-proconnect': true,
beta: true,
favicon: {
ico: '/assets/favicon-dsfr.ico',
'png-light': '/assets/favicon-dsfr.png',
'png-dark': '/assets/favicon-dark-dsfr.png',
},
},
},
generic: {
theme: {
colors: {
'primary-action': '#206EBD',
'primary-focus': '#1E64BF',
'primary-text': '#2E2C28',
'primary-050': '#F8F8F7',
'primary-100': '#F0EFEC',
'primary-150': '#F4F4FD',
'primary-200': '#E8E7E4',
'primary-300': '#CFCDC9',
'primary-400': '#979592',
'primary-500': '#82807D',
'primary-600': '#3F3D39',
'primary-700': '#2E2C28',
'primary-800': '#302E29',
'primary-900': '#282622',
'primary-950': '#201F1C',
'secondary-text': '#fff',
'secondary-50': '#F4F7FA',
'secondary-100': '#D7E3EE',
'secondary-200': '#B8CCE1',
'secondary-300': '#99B4D3',
'secondary-400': '#7595BE',
'secondary-500': '#5874A0',
'secondary-600': '#3A5383',
'secondary-700': '#1E3462',
'secondary-800': '#091B41',
'secondary-900': '#08183B',
'secondary-950': '#071636',
'greyscale-text': '#3C3B38',
'greyscale-000': '#fff',
'greyscale-050': '#F8F7F7',
'greyscale-100': '#F3F3F2',
'greyscale-200': '#ECEBEA',
'greyscale-250': '#E4E3E2',
'greyscale-300': '#D3D2CF',
'greyscale-350': '#eee',
'greyscale-400': '#96948E',
'greyscale-500': '#817E77',
'greyscale-600': '#6A6862',
'greyscale-700': '#3C3B38',
'greyscale-750': '#383632',
'greyscale-800': '#2D2B27',
'greyscale-900': '#262522',
'greyscale-950': '#201F1C',
'greyscale-1000': '#181714',
'success-text': '#234935',
'success-50': '#F3FBF5',
'success-100': '#E4F7EA',
'success-200': '#CAEED4',
'success-300': '#A0E0B5',
'success-400': '#6CC88C',
'success-500': '#6CC88C',
'success-600': '#358D5C',
'success-700': '#2D704B',
'success-800': '#28583F',
'success-900': '#234935',
'success-950': '#0F281B',
'info-text': '#212445',
'info-50': '#F2F6FB',
'info-100': '#E2E9F5',
'info-200': '#CCD8EE',
'info-300': '#A9C0E3',
'info-400': '#809DD4',
'info-500': '#617BC7',
'info-600': '#4A5CBF',
'info-700': '#3E49B2',
'info-800': '#353C8F',
'info-900': '#303771',
'info-950': '#212445',
'warning-text': '#D97C3A',
'warning-50': '#FDF7F1',
'warning-100': '#FBEDDC',
'warning-200': '#F5D9B9',
'warning-300': '#EDBE8C',
'warning-400': '#E2985C',
'warning-500': '#D97C3A',
'warning-600': '#C96330',
'warning-700': '#A34B32',
'warning-800': '#813B2C',
'warning-900': '#693327',
'warning-950': '#381713',
'danger-action': '#C0182A',
'danger-text': '#FFF',
'danger-050': '#FDF5F4',
'danger-100': '#FBEBE8',
'danger-200': '#F9E0DC',
'danger-300': '#F3C3BD',
'danger-400': '#E26552',
'danger-500': '#C91F00',
'danger-600': '#A71901',
'danger-700': '#562C2B',
'danger-800': '#392425',
'danger-900': '#311F20',
'danger-950': '#2A191A',
'blue-400': '#8BAECC',
'blue-500': '#567AA2',
'blue-600': '#455784',
'brown-400': '#E4C090',
'brown-500': '#BA9977',
'brown-600': '#735C45',
'cyan-400': '#5CBEC9',
'cyan-500': '#43A1B3',
'cyan-600': '#39809B',
'gold-400': '#ECBF50',
'gold-500': '#DFA038',
'gold-600': '#C17B31',
'green-400': '#5DBD9A',
'green-500': '#3AA183',
'green-600': '#2A816D',
'olive-400': '#AFD662',
'olive-500': '#90BB4B',
'olive-600': '#6E9441',
'orange-400': '#E2985C',
'orange-500': '#D97C3A',
'orange-600': '#C96330',
'pink-400': '#BE8FC8',
'pink-500': '#A563B1',
'pink-600': '#8B44A5',
'purple-400': '#BE8FC8',
'purple-500': '#A563B1',
'purple-600': '#8B44A5',
'yellow-400': '#EDC947',
'yellow-500': '#DBB13A',
'yellow-600': '#B88A34',
},
font: {
families: {
base: 'Inter, Roboto Flex Variable, sans-serif',
accent: 'Inter, Roboto Flex Variable, sans-serif',
},
},
},
components: {
button: {
primary: {
background: {
'color-hover': 'var(--c--theme--colors--primary-focus)',
'color-active': 'var(--c--theme--colors--primary-focus)',
'color-focus': 'var(--c--theme--colors--primary-focus)',
},
},
},
'image-system-filter': 'saturate(0.2)',
},
},
},
};
@@ -12,30 +12,32 @@ type ComponentTokens = Tokens['components'];
export type Theme = keyof typeof tokens.themes;
interface ThemeStore {
theme: Theme;
setTheme: (theme: Theme) => void;
themeTokens: Partial<Tokens['theme']>;
colorsTokens: Partial<ColorsTokens>;
fontSizesTokens: Partial<FontSizesTokens>;
spacingsTokens: Partial<SpacingsTokens>;
componentTokens: ComponentTokens;
currentTokens: Partial<Tokens>;
fontSizesTokens: Partial<FontSizesTokens>;
setTheme: (theme: Theme) => void;
spacingsTokens: Partial<SpacingsTokens>;
theme: Theme;
themeTokens: Partial<Tokens['theme']>;
}
const getMergedTokens = (theme: Theme) => {
return merge({}, tokens.themes['default'], tokens.themes[theme]);
};
const DEFAULT_THEME: Theme = 'default';
const DEFAULT_THEME: Theme = 'generic';
const defaultTokens = getMergedTokens(DEFAULT_THEME);
const initialState: ThemeStore = {
theme: DEFAULT_THEME,
setTheme: () => {},
themeTokens: defaultTokens.theme,
colorsTokens: defaultTokens.theme.colors,
componentTokens: defaultTokens.components,
spacingsTokens: defaultTokens.theme.spacings,
currentTokens: tokens.themes[DEFAULT_THEME] as Partial<Tokens>,
fontSizesTokens: defaultTokens.theme.font.sizes,
setTheme: () => {},
spacingsTokens: defaultTokens.theme.spacings,
theme: DEFAULT_THEME,
themeTokens: defaultTokens.theme,
};
export const useCunninghamTheme = create<ThemeStore>((set) => ({
@@ -44,12 +46,13 @@ export const useCunninghamTheme = create<ThemeStore>((set) => ({
const newTokens = getMergedTokens(theme);
set({
theme,
themeTokens: newTokens.theme,
colorsTokens: newTokens.theme.colors,
componentTokens: newTokens.components,
spacingsTokens: newTokens.theme.spacings,
currentTokens: tokens.themes[theme] as Partial<Tokens>,
fontSizesTokens: newTokens.theme.font.sizes,
spacingsTokens: newTokens.theme.spacings,
theme,
themeTokens: newTokens.theme,
});
},
}));
+7 -1
View File
@@ -13,7 +13,13 @@ declare module '*.svg' {
}
declare module '*.svg?url' {
const content: string;
const content: {
src: string;
width: number;
height: number;
blurWidth: number;
blurHeight: number;
};
export default content;
}
@@ -0,0 +1,27 @@
import { APIError, errorCauses } from '@/api';
interface CheckDocMediaStatusResponse {
file?: string;
status: 'processing' | 'ready';
}
interface CheckDocMediaStatus {
urlMedia: string;
}
export const checkDocMediaStatus = async ({
urlMedia,
}: CheckDocMediaStatus): Promise<CheckDocMediaStatusResponse> => {
const response = await fetch(urlMedia, {
credentials: 'include',
});
if (!response.ok) {
throw new APIError(
'Failed to check the media status',
await errorCauses(response),
);
}
return response.json() as Promise<CheckDocMediaStatusResponse>;
};
@@ -1,3 +1,4 @@
export * from './checkDocMediaStatus';
export * from './useCreateDocUpload';
export * from './useDocAITransform';
export * from './useDocAITranslate';
@@ -0,0 +1,27 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#3b82f6"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
class="analyzing-spinner"
>
<circle cx="12" cy="12" r="10" opacity="0.2"></circle>
<path d="M12 2a10 10 0 0 1 10 10" opacity="0.8">
<animateTransform
attributeName="transform"
type="rotate"
from="0 12 12"
to="360 12 12"
dur="1s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.42 0 0.58 1"
keyTimes="0;1"
/>
</path>
</svg>

After

Width:  |  Height:  |  Size: 598 B

@@ -0,0 +1,17 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
fill="#FFA500"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
></path>
<line x1="12" y1="9" x2="12" y2="13"></line>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>

After

Width:  |  Height:  |  Size: 425 B

@@ -18,16 +18,17 @@ import { Box, TextErrors } from '@/components';
import { Doc, useIsCollaborativeEditable } from '@/docs/doc-management';
import { useAuth } from '@/features/auth';
import { useUploadFile } from '../hook';
import { useHeadings } from '../hook/useHeadings';
import { useHeadings, useUploadFile, useUploadStatus } from '../hook/';
import useSaveDoc from '../hook/useSaveDoc';
import { useEditorStore } from '../stores';
import { cssEditor } from '../styles';
import { DocsBlockNoteEditor } from '../types';
import { randomColor } from '../utils';
import { BlockNoteSuggestionMenu } from './BlockNoteSuggestionMenu';
import { BlockNoteToolbar } from './BlockNoteToolBar/BlockNoteToolbar';
import { CalloutBlock, DividerBlock } from './custom-blocks';
import { DrawBlock } from './custom-blocks/DrawBlock';
export const blockNoteSchema = withPageBreak(
BlockNoteSchema.create({
@@ -35,6 +36,7 @@ export const blockNoteSchema = withPageBreak(
...defaultBlockSpecs,
callout: CalloutBlock,
divider: DividerBlock,
draw: DrawBlock,
},
}),
);
@@ -63,7 +65,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
: user?.full_name || user?.email || t('Anonymous');
const showCursorLabels: 'always' | 'activity' | (string & {}) = 'activity';
const editor = useCreateBlockNote(
const editor: DocsBlockNoteEditor = useCreateBlockNote(
{
codeBlock,
collaboration: {
@@ -127,7 +129,9 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
},
[collabName, lang, provider, uploadFile],
);
useHeadings(editor);
useUploadStatus(editor);
useEffect(() => {
setEditor(editor);
@@ -15,6 +15,7 @@ import {
getCalloutReactSlashMenuItems,
getDividerReactSlashMenuItems,
} from './custom-blocks';
import { getDrawReactSlashMenuItems } from './custom-blocks/DrawBlock';
export const BlockNoteSuggestionMenu = () => {
const editor = useBlockNoteEditor<DocsBlockSchema>();
@@ -30,6 +31,7 @@ export const BlockNoteSuggestionMenu = () => {
getPageBreakReactSlashMenuItems(editor),
getCalloutReactSlashMenuItems(editor, t, basicBlocksName),
getDividerReactSlashMenuItems(editor, t, basicBlocksName),
getDrawReactSlashMenuItems(editor, t, basicBlocksName),
),
query,
),
@@ -32,7 +32,6 @@ export const ModalConfirmDownloadUnsafe = ({
aria-label={t('Download')}
color="danger"
onClick={() => {
console.log('onClick');
if (onConfirm) {
void onConfirm();
}
@@ -0,0 +1,341 @@
/* eslint-disable react-hooks/rules-of-hooks */
import {
ReactRendererProps,
defaultProps,
insertOrUpdateBlock,
} from '@blocknote/core';
import { BlockTypeSelectItem, createReactBlockSpec } from '@blocknote/react';
import { TFunction } from 'i18next';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
Editor,
TLEventMapHandler,
TLStore,
Tldraw,
getSnapshot,
loadSnapshot,
} from 'tldraw';
import 'tldraw/tldraw.css';
import { Box, Icon } from '@/components';
import { DocsBlockNoteEditor } from '../../types';
import _ from 'lodash';
import { clear } from 'console';
/**
* ----------------------------------------------------------------------------------
* Collaborative **Draw** block backed by a Yjs document synced through
* `@hocuspocus/provider` (see `useProviderStore`).
* ----------------------------------------------------------------------------------
*
* Each Draw block owns its own YDoc, identified by `roomId` (persisted in `propSchema`).
* The block serialises a base64encoded Yjs update (`drawingData`) so newcomers see
* the latest snapshot _immediately_, without waiting for the websocket connection.
*/
export const DrawBlock = createReactBlockSpec(
{
type: 'draw',
propSchema: {
textAlignment: defaultProps.textAlignment,
backgroundColor: defaultProps.backgroundColor,
roomId: { default: `drawing-${Date.now()}` },
drawingData: { default: '' },
changeHistory: { default: '' },
lastChange: { default: '' },
increment: { default: 0 }, // Increment to force re-rendering
},
content: 'inline',
},
{
render: ({ block, editor: editorBN }) => {
const [editor, setEditor] = useState<Editor>();
const setAppToState = useCallback((editor: Editor) => {
setEditor(editor);
}, []);
const timeoutId = useRef<NodeJS.Timeout | null>(null);
const [storeEvents, setStoreEvents] = useState<string[]>([]);
console.log('Loading saved drawing data');
useEffect(() => {
if (block.props.drawingData && editor) {
console.log('DDData');
try {
const drawingData = JSON.parse(block.props.drawingData);
//const drawingData = block.props.drawingData;
// Update: Using the non-deprecated method to load the snapshot
// Instead of editor.store.loadSnapshot(drawingData)
loadSnapshot(editor.store, drawingData);
console.log('Successfully loaded drawing data');
} catch (error) {
console.error('Failed to load drawing data:', error);
}
}
}, [block.props.drawingData, editor]);
useEffect(() => {
if (!editor) {
return;
}
// Load saved drawing data if available
function logChangeEvent(eventInfo: string, changeData: any = null) {
console.log(eventInfo);
// Get current properties
// const currentProps = { ...block.props };
// // Get current changeHistory or initialize empty array
// const currentHistory = Array.isArray(currentProps.changeHistory)
// ? currentProps.changeHistory
// : [];
// // Create a change record with timestamp
// const changeRecord = {
// timestamp: new Date().toISOString(),
// event: eventInfo,
// data: changeData,
// };
// // Create new props object with all updated values
// const updatedProps = {
// ...currentProps,
// changeHistory: [...currentHistory, changeRecord],
// lastChange: changeRecord,
// };
// // Add drawingData if available
// if (changeData?.drawingData) {
// updatedProps.drawingData = changeData.drawingData;
// }
// Update the block with the new props
// console.log('Updating block with props:', updatedProps);
// //editorBN.updateBlock(block, { props: updatedProps });
// if (timeoutId.current) {
// clearTimeout(timeoutId.current);
// }
// timeoutId.current = setTimeout(() => {
// editorBN.updateBlock(block, {
// props: updatedProps,
// });
// }, 300);
if (!editor) {
return;
}
if (timeoutId.current) {
clearTimeout(timeoutId.current);
}
timeoutId.current = setTimeout(() => {
const snapshot = getSnapshot(editor.store);
//const snapshot = JSON.stringify(editor.store.serialize());
console.log('Captured drawing snapshot:', snapshot);
// Only update drawingData property to avoid multiple updates
const currentProps = { ...block.props };
editorBN.updateBlock(block, {
props: {
drawingData: JSON.stringify(snapshot),
increment: currentProps.increment + 1, // Increment to force re-rendering
},
});
}, 300);
//setStoreEvents((events) => [...events, eventInfo]);
}
//[1]
const handleChangeEvent: TLEventMapHandler<'change'> = (change) => {
// Added
for (const record of Object.values(change.changes.added)) {
if (record.typeName === 'shape') {
logChangeEvent(`created shape (${record.type})`, {
action: 'created',
shapeType: record.type,
shape: record,
});
}
}
// Updated
for (const [from, to] of Object.values(change.changes.updated)) {
if (
from.typeName === 'instance' &&
to.typeName === 'instance' &&
from.currentPageId !== to.currentPageId
) {
logChangeEvent(
`changed page (${from.currentPageId}, ${to.currentPageId})`,
{
action: 'changedPage',
fromPageId: from.currentPageId,
toPageId: to.currentPageId,
},
);
} else if (
from.id.startsWith('shape') &&
to.id.startsWith('shape')
) {
let diff = _.reduce(
from,
(result: any[], value, key: string) =>
_.isEqual(value, to[key])
? result
: result.concat([key, to[key]]),
[],
);
const diffObj = {};
if (diff?.[0] === 'props') {
diff = _.reduce(
from.props,
(result: any[], value, key) =>
_.isEqual(value, to.props[key])
? result
: result.concat([key, to.props[key]]),
[],
);
// Convert diff array to object for better storage
for (let i = 0; i < diff.length; i += 2) {
diffObj[diff[i]] = diff[i + 1];
}
}
logChangeEvent(`updated shape (${JSON.stringify(diff)})`, {
action: 'updated',
shapeId: from.id,
changes: diffObj,
from: from,
to: to,
});
}
}
// Removed
for (const record of Object.values(change.changes.removed)) {
if (record.typeName === 'shape') {
logChangeEvent(`deleted shape (${record.type})`, {
action: 'deleted',
shapeType: record.type,
shape: record,
});
}
}
// Store the entire drawing state periodically when changes occur
// if (
// Object.keys(change.changes.added).length > 0 ||
// Object.keys(change.changes.updated).length > 0 ||
// Object.keys(change.changes.removed).length > 0
// ) {
// // Capture the current drawing state if available
// if (editor.store) {
// try {
// // Update: Using the non-deprecated method to get the snapshot
// // Instead of
// //
// const snapshot = getSnapshot(editor.store);
// //const snapshot = JSON.stringify(editor.store.serialize());
// console.log('Captured drawing snapshot:', snapshot);
// // Only update drawingData property to avoid multiple updates
// const currentProps = { ...block.props };
// if (timeoutId.current) {
// clearTimeout(timeoutId.current);
// }
// timeoutId.current = setTimeout(() => {
// editorBN.updateBlock(block, {
// props: {
// ...currentProps,
// drawingData: snapshot,
// },
// });
// }, 300);
// console.log('Drawing snapshot updated');
// } catch (error) {
// console.error('Failed to capture drawing snapshot:', error);
// }
// }
// }
};
// [2]
const cleanupFunction = editor.store.listen(handleChangeEvent, {
source: 'user',
scope: 'all',
});
return () => {
cleanupFunction();
};
}, [block, editor, editorBN]);
return (
<Box style={{ width: '100%', height: 300 }}>
{/*
* We deliberately pass the TLstore directly. TLDraw will observe the
* changes including those coming over the wire and rerender.
*/}
<Tldraw onMount={setAppToState} />
</Box>
);
},
},
);
/**
* Slashmenu helper inserts a new collaborative draw block with a unique `roomId`.
*/
export const getDrawReactSlashMenuItems = (
editor: DocsBlockNoteEditor,
t: TFunction<'translation', undefined>,
group: string,
) => [
{
title: t('Draw'),
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: 'draw',
props: {
roomId: `drawing-${Date.now()}`,
drawingData: null,
changeHistory: [],
lastChange: null,
},
});
},
aliases: ['draw'],
group,
icon: <Icon iconName="draw" $size="18px" />,
subtext: t('Add a collaborative canvas'),
},
];
/**
* Formattingtoolbar item so users can transform an existing block into a Draw block.
*/
export const getDrawFormattingToolbarItems = (
t: TFunction<'translation', undefined>,
): BlockTypeSelectItem => ({
name: t('Draw'),
type: 'draw',
icon: () => <Icon iconName="lightbulb" $size="16px" />,
isSelected: (block) => block.type === 'draw',
});
@@ -1,2 +1,3 @@
export * from './useHeadings';
export * from './useSaveDoc';
export * from './useUploadFile';
@@ -1,11 +1,85 @@
import { useCallback } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { backendUrl } from '@/api';
import { useMediaUrl } from '@/core/config';
import { sleep } from '@/utils';
import { useCreateDocAttachment } from '../api';
import { checkDocMediaStatus, useCreateDocAttachment } from '../api';
import Loader from '../assets/loader.svg?url';
import Warning from '../assets/warning.svg?url';
import { DocsBlockNoteEditor } from '../types';
/**
* Upload file can be analyzed on the server side,
* we had this function to wait for the analysis to be done
* before returning the file url. It will keep the loader
* on the upload button until the analysis is done.
* @param url
* @returns Promise<CheckDocMediaStatusResponse> status_code
* @description Waits for the upload to be analyzed by checking the status of the file.
*/
const loopCheckDocMediaStatus = async (url: string) => {
const SLEEP_TIME = 5000;
const response = await checkDocMediaStatus({
urlMedia: url,
});
if (response.status === 'ready') {
return response;
} else {
await sleep(SLEEP_TIME);
return await loopCheckDocMediaStatus(url);
}
};
const informationStatus = (src: string, text: string) => {
const loadingContainer = document.createElement('div');
loadingContainer.style.display = 'flex';
loadingContainer.style.alignItems = 'center';
loadingContainer.style.justifyContent = 'left';
loadingContainer.style.padding = '10px';
loadingContainer.style.color = '#666';
loadingContainer.className =
'bn-visual-media bn-audio bn-file-name-with-icon';
// Create an image element for the SVG
const imgElement = document.createElement('img');
imgElement.src = src;
// Create a text span
const textSpan = document.createElement('span');
textSpan.textContent = text;
textSpan.style.marginLeft = '8px';
textSpan.style.verticalAlign = 'middle';
imgElement.style.animation = 'spin 1.5s linear infinite';
// Add the spinner and text to the container
loadingContainer.appendChild(imgElement);
loadingContainer.appendChild(textSpan);
return loadingContainer;
};
const replaceUploadContent = (blockId: string, elementReplace: HTMLElement) => {
const blockEl = document.body.querySelector(
`.bn-block[data-id="${blockId}"]`,
);
blockEl
?.querySelector('.bn-visual-media-wrapper .bn-visual-media')
?.replaceWith(elementReplace);
blockEl
?.querySelector('.bn-file-block-content-wrapper .bn-audio')
?.replaceWith(elementReplace);
blockEl
?.querySelector('.bn-file-block-content-wrapper .bn-file-name-with-icon')
?.replaceWith(elementReplace);
};
export const useUploadFile = (docId: string) => {
const mediaUrl = useMediaUrl();
const {
mutateAsync: createDocAttachment,
isError: isErrorAttachment,
@@ -22,9 +96,9 @@ export const useUploadFile = (docId: string) => {
body,
});
return `${mediaUrl}${ret.file}`;
return `${backendUrl()}${ret.file}`;
},
[createDocAttachment, docId, mediaUrl],
[createDocAttachment, docId],
);
return {
@@ -33,3 +107,101 @@ export const useUploadFile = (docId: string) => {
errorAttachment,
};
};
export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
const ANALYZE_URL = 'media-check';
const { t } = useTranslation();
const mediaUrl = useMediaUrl();
const timeoutIds = useRef<Record<string, NodeJS.Timeout>>({});
const blockAnalyzeProcess = useCallback(
(editor: DocsBlockNoteEditor, blockId: string, url: string) => {
if (timeoutIds.current[url]) {
clearTimeout(timeoutIds.current[url]);
}
// Delay to let the time to the dom to be rendered
const timoutId = setTimeout(() => {
replaceUploadContent(
blockId,
informationStatus(Loader.src, t('Analyzing file...')),
);
loopCheckDocMediaStatus(url)
.then((response) => {
const block = editor.getBlock(blockId);
if (!block) {
return;
}
block.props = {
...block.props,
url: `${mediaUrl}${response.file}`,
};
editor.updateBlock(blockId, block);
})
.catch((error) => {
console.error('Error analyzing file:', error);
replaceUploadContent(
blockId,
informationStatus(
Warning.src,
t('The antivirus has detected an anomaly in your file.'),
),
);
});
}, 250);
timeoutIds.current[url] = timoutId;
},
[t, mediaUrl],
);
useEffect(() => {
const blocksAnalyze = editor?.document.filter(
(block) => 'url' in block.props && block.props.url.includes(ANALYZE_URL),
);
if (!blocksAnalyze?.length) {
return;
}
blocksAnalyze.forEach((block) => {
if (!('url' in block.props)) {
return;
}
blockAnalyzeProcess(editor, block.id, block.props.url);
});
}, [blockAnalyzeProcess, editor]);
useEffect(() => {
editor.onChange((_, context) => {
const blocksChanges = context.getChanges();
if (!blocksChanges.length) {
return;
}
const blockChanges = blocksChanges[0];
if (
blockChanges.source.type !== 'local' ||
blockChanges.type !== 'update' ||
!('url' in blockChanges.block.props) ||
('url' in blockChanges.block.props &&
!blockChanges.block.props.url.includes(ANALYZE_URL))
) {
return;
}
blockAnalyzeProcess(
editor,
blockChanges.block.id,
blockChanges.block.props.url,
);
});
}, [blockAnalyzeProcess, mediaUrl, editor, t]);
};
@@ -29,7 +29,7 @@ export const AlertNetwork = () => {
border: 1px solid var(--c--theme--colors--warning-300);
`}
>
<Box $direction="row" $gap={spacingsTokens['2xs']}>
<Box $direction="row" $gap={spacingsTokens['2xs']} $align="center">
<Icon iconName="mobiledata_off" $theme="warning" $variation="600" />
<Text $theme="warning" $variation="600" $weight={500}>
{t('Your network do not allow you to edit')}
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { useConfig } from '@/core';
import { useIsOffline } from '@/features/service-worker';
import { useProviderStore } from '../stores';
@@ -7,6 +8,7 @@ import { Doc, LinkReach } from '../types';
export const useIsCollaborativeEditable = (doc: Doc) => {
const { isConnected } = useProviderStore();
const { data: conf } = useConfig();
const docIsPublic = doc.link_reach === LinkReach.PUBLIC;
const docIsAuth = doc.link_reach === LinkReach.AUTHENTICATED;
@@ -37,6 +39,13 @@ export const useIsCollaborativeEditable = (doc: Doc) => {
return () => clearTimeout(timer);
}, [isConnected, isOffline, isShared]);
if (!conf?.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY) {
return {
isEditable: true,
isLoading: false,
};
}
return {
isEditable,
isLoading,
@@ -87,6 +87,7 @@ export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
$justify="center"
>
<Image
className="c__image-system-filter"
width={320}
src={EmptySearchIcon}
alt={t('No active search')}
@@ -1,51 +1,138 @@
<svg
width="32"
height="36"
viewBox="0 0 32 36"
width="33"
height="33"
viewBox="0 0 33 33"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
fill="white"
/>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
stroke="#E3E3FD"
stroke-width="0.472222"
/>
<path
d="M6.5 8.55554H15"
stroke="#6A6AF4"
stroke-width="1.88889"
stroke-linecap="round"
/>
<path
d="M6.5 11.3889H23.5M6.5 14.2222H23.5M6.5 17.0556H23.5M6.5 19.8889H23.5M6.5 22.7222H20.6667"
stroke="#CACAFB"
stroke-width="1.88889"
stroke-linecap="round"
/>
<rect x="7" y="10" width="16" height="16" rx="8" fill="#6A6AF4" />
<rect
x="7"
y="10"
width="16"
height="16"
rx="8"
stroke="white"
stroke-width="1.5"
/>
<path
d="M16.8 18L18 19.2V20.1H15.45V22.95L15 23.4L14.55 22.95V20.1H12V19.2L13.2 18V14.7H12.6V13.8H17.4V14.7H16.8V18Z"
fill="white"
/>
<g clip-path="url(#clip0_3236_2932)">
<g clip-path="url(#clip1_3236_2932)">
<rect x="4.5" y="0.5" width="24" height="32" rx="3.55556" fill="white" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 7.7623C8.08374 7.27138 8.48171 6.87341 8.97263 6.87341H16.9726C17.4635 6.87341 17.8615 7.27138 17.8615 7.7623C17.8615 8.25322 17.4635 8.65118 16.9726 8.65118H8.97263C8.48171 8.65118 8.08374 8.25322 8.08374 7.7623Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 10.7685C8.08374 10.2776 8.48171 9.87964 8.97263 9.87964H24.0273C24.5182 9.87964 24.9162 10.2776 24.9162 10.7685C24.9162 11.2594 24.5182 11.6574 24.0273 11.6574H8.97263C8.48171 11.6574 8.08374 11.2594 8.08374 10.7685ZM8.08374 13.4352C8.08374 12.9443 8.48171 12.5463 8.97263 12.5463H24.0273C24.5182 12.5463 24.9162 12.9443 24.9162 13.4352C24.9162 13.9261 24.5182 14.3241 24.0273 14.3241H8.97263C8.48171 14.3241 8.08374 13.9261 8.08374 13.4352ZM8.08374 16.1019C8.08374 15.6109 8.48171 15.213 8.97263 15.213H24.0273C24.5182 15.213 24.9162 15.6109 24.9162 16.1019C24.9162 16.5928 24.5182 16.9907 24.0273 16.9907H8.97263C8.48171 16.9907 8.08374 16.5928 8.08374 16.1019ZM8.08374 18.7685C8.08374 18.2776 8.48171 17.8796 8.97263 17.8796H24.0273C24.5182 17.8796 24.9162 18.2776 24.9162 18.7685C24.9162 19.2594 24.5182 19.6574 24.0273 19.6574H8.97263C8.48171 19.6574 8.08374 19.2594 8.08374 18.7685Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 10.7685C8.08374 10.2776 8.48171 9.87964 8.97263 9.87964H24.0273C24.5182 9.87964 24.9162 10.2776 24.9162 10.7685C24.9162 11.2594 24.5182 11.6574 24.0273 11.6574H8.97263C8.48171 11.6574 8.08374 11.2594 8.08374 10.7685ZM8.08374 13.4352C8.08374 12.9443 8.48171 12.5463 8.97263 12.5463H24.0273C24.5182 12.5463 24.9162 12.9443 24.9162 13.4352C24.9162 13.9261 24.5182 14.3241 24.0273 14.3241H8.97263C8.48171 14.3241 8.08374 13.9261 8.08374 13.4352ZM8.08374 16.1019C8.08374 15.6109 8.48171 15.213 8.97263 15.213H24.0273C24.5182 15.213 24.9162 15.6109 24.9162 16.1019C24.9162 16.5928 24.5182 16.9907 24.0273 16.9907H8.97263C8.48171 16.9907 8.08374 16.5928 8.08374 16.1019ZM8.08374 18.7685C8.08374 18.2776 8.48171 17.8796 8.97263 17.8796H24.0273C24.5182 17.8796 24.9162 18.2776 24.9162 18.7685C24.9162 19.2594 24.5182 19.6574 24.0273 19.6574H8.97263C8.48171 19.6574 8.08374 19.2594 8.08374 18.7685Z"
fill="white"
fill-opacity="0.65"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 21.4666C8.08374 20.9757 8.48171 20.5777 8.97263 20.5777H24.0273C24.5182 20.5777 24.9162 20.9757 24.9162 21.4666C24.9162 21.9575 24.5182 22.3555 24.0273 22.3555H8.97263C8.48171 22.3555 8.08374 21.9575 8.08374 21.4666ZM8.08374 24.1333C8.08374 23.6424 8.48171 23.2444 8.97263 23.2444H24.0273C24.5182 23.2444 24.9162 23.6424 24.9162 24.1333C24.9162 24.6242 24.5182 25.0222 24.0273 25.0222H8.97263C8.48171 25.0222 8.08374 24.6242 8.08374 24.1333ZM8.08374 26.8C8.08374 26.309 8.48171 25.9111 8.97263 25.9111H24.0273C24.5182 25.9111 24.9162 26.309 24.9162 26.8C24.9162 27.2909 24.5182 27.6888 24.0273 27.6888H8.97263C8.48171 27.6888 8.08374 27.2909 8.08374 26.8ZM8.08374 29.4666C8.08374 28.9757 8.48171 28.5777 8.97263 28.5777H24.0273C24.5182 28.5777 24.9162 28.9757 24.9162 29.4666C24.9162 29.9575 24.5182 30.3555 24.0273 30.3555H8.97263C8.48171 30.3555 8.08374 29.9575 8.08374 29.4666Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 21.4666C8.08374 20.9757 8.48171 20.5777 8.97263 20.5777H24.0273C24.5182 20.5777 24.9162 20.9757 24.9162 21.4666C24.9162 21.9575 24.5182 22.3555 24.0273 22.3555H8.97263C8.48171 22.3555 8.08374 21.9575 8.08374 21.4666ZM8.08374 24.1333C8.08374 23.6424 8.48171 23.2444 8.97263 23.2444H24.0273C24.5182 23.2444 24.9162 23.6424 24.9162 24.1333C24.9162 24.6242 24.5182 25.0222 24.0273 25.0222H8.97263C8.48171 25.0222 8.08374 24.6242 8.08374 24.1333ZM8.08374 26.8C8.08374 26.309 8.48171 25.9111 8.97263 25.9111H24.0273C24.5182 25.9111 24.9162 26.309 24.9162 26.8C24.9162 27.2909 24.5182 27.6888 24.0273 27.6888H8.97263C8.48171 27.6888 8.08374 27.2909 8.08374 26.8ZM8.08374 29.4666C8.08374 28.9757 8.48171 28.5777 8.97263 28.5777H24.0273C24.5182 28.5777 24.9162 28.9757 24.9162 29.4666C24.9162 29.9575 24.5182 30.3555 24.0273 30.3555H8.97263C8.48171 30.3555 8.08374 29.9575 8.08374 29.4666Z"
fill="white"
fill-opacity="0.65"
/>
<rect
x="4.57422"
y="0.5"
width="23.9258"
height="31.8206"
fill="url(#paint0_linear_3236_2932)"
fill-opacity="0.4"
/>
</g>
<rect
x="4.85"
y="0.85"
width="23.3"
height="31.3"
rx="3.20556"
stroke="currentColor"
stroke-width="0.7"
/>
<rect
x="4.85"
y="0.85"
width="23.3"
height="31.3"
rx="3.20556"
stroke="white"
stroke-opacity="0.65"
stroke-width="0.7"
/>
<rect
x="4.85"
y="0.85"
width="23.3"
height="31.3"
rx="3.20556"
stroke="url(#paint1_linear_3236_2932)"
stroke-opacity="0.23"
stroke-width="0.7"
/>
<rect
x="10.0132"
y="10.0132"
width="12.9736"
height="12.9736"
rx="6.48682"
fill="currentColor"
/>
<rect
x="10.0132"
y="10.0132"
width="12.9736"
height="12.9736"
rx="6.48682"
stroke="white"
stroke-width="1.21628"
/>
<path
d="M17.9595 16.5L18.9325 17.473V18.2028H16.8648V20.5137L16.4999 20.8786L16.1351 20.5137V18.2028H14.0674V17.473L15.0404 16.5V13.8242H14.5539V13.0944H18.446V13.8242H17.9595V16.5Z"
fill="white"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_3236_2932"
x1="16.5371"
y1="0.5"
x2="16.5371"
y2="32.3206"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0" />
<stop offset="1" stop-color="white" />
</linearGradient>
<linearGradient
id="paint1_linear_3236_2932"
x1="16.5"
y1="0.5"
x2="16.5"
y2="32.5"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0" />
<stop offset="1" stop-color="white" />
</linearGradient>
<clipPath id="clip0_3236_2932">
<rect
width="32"
height="32"
fill="white"
transform="translate(0.5 0.5)"
/>
</clipPath>
<clipPath id="clip1_3236_2932">
<rect x="4.5" y="0.5" width="24" height="32" rx="3.55556" fill="white" />
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1017 B

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -1,37 +1,117 @@
<svg
width="32"
height="36"
viewBox="0 0 32 36"
width="33"
height="33"
viewBox="0 0 33 33"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
fill="white"
/>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
stroke="#E3E3FD"
stroke-width="0.472222"
/>
<path
d="M6.5 8.55554H15"
stroke="#6A6AF4"
stroke-width="1.88889"
stroke-linecap="round"
/>
<path
d="M6.5 11.3889H23.5M6.5 14.2222H23.5M6.5 17.0556H23.5M6.5 19.8889H23.5M6.5 22.7222H20.6667"
stroke="#CACAFB"
stroke-width="1.88889"
stroke-linecap="round"
/>
<g clip-path="url(#clip0_3236_2952)">
<g clip-path="url(#clip1_3236_2952)">
<rect x="4.5" y="0.5" width="24" height="32" rx="3.55556" fill="white" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 7.7623C8.08374 7.27138 8.48171 6.87341 8.97263 6.87341H16.9726C17.4635 6.87341 17.8615 7.27138 17.8615 7.7623C17.8615 8.25322 17.4635 8.65118 16.9726 8.65118H8.97263C8.48171 8.65118 8.08374 8.25322 8.08374 7.7623Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 10.7685C8.08374 10.2776 8.48171 9.87964 8.97263 9.87964H24.0273C24.5182 9.87964 24.9162 10.2776 24.9162 10.7685C24.9162 11.2594 24.5182 11.6574 24.0273 11.6574H8.97263C8.48171 11.6574 8.08374 11.2594 8.08374 10.7685ZM8.08374 13.4352C8.08374 12.9443 8.48171 12.5463 8.97263 12.5463H24.0273C24.5182 12.5463 24.9162 12.9443 24.9162 13.4352C24.9162 13.9261 24.5182 14.3241 24.0273 14.3241H8.97263C8.48171 14.3241 8.08374 13.9261 8.08374 13.4352ZM8.08374 16.1019C8.08374 15.6109 8.48171 15.213 8.97263 15.213H24.0273C24.5182 15.213 24.9162 15.6109 24.9162 16.1019C24.9162 16.5928 24.5182 16.9907 24.0273 16.9907H8.97263C8.48171 16.9907 8.08374 16.5928 8.08374 16.1019ZM8.08374 18.7685C8.08374 18.2776 8.48171 17.8796 8.97263 17.8796H24.0273C24.5182 17.8796 24.9162 18.2776 24.9162 18.7685C24.9162 19.2594 24.5182 19.6574 24.0273 19.6574H8.97263C8.48171 19.6574 8.08374 19.2594 8.08374 18.7685Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 10.7685C8.08374 10.2776 8.48171 9.87964 8.97263 9.87964H24.0273C24.5182 9.87964 24.9162 10.2776 24.9162 10.7685C24.9162 11.2594 24.5182 11.6574 24.0273 11.6574H8.97263C8.48171 11.6574 8.08374 11.2594 8.08374 10.7685ZM8.08374 13.4352C8.08374 12.9443 8.48171 12.5463 8.97263 12.5463H24.0273C24.5182 12.5463 24.9162 12.9443 24.9162 13.4352C24.9162 13.9261 24.5182 14.3241 24.0273 14.3241H8.97263C8.48171 14.3241 8.08374 13.9261 8.08374 13.4352ZM8.08374 16.1019C8.08374 15.6109 8.48171 15.213 8.97263 15.213H24.0273C24.5182 15.213 24.9162 15.6109 24.9162 16.1019C24.9162 16.5928 24.5182 16.9907 24.0273 16.9907H8.97263C8.48171 16.9907 8.08374 16.5928 8.08374 16.1019ZM8.08374 18.7685C8.08374 18.2776 8.48171 17.8796 8.97263 17.8796H24.0273C24.5182 17.8796 24.9162 18.2776 24.9162 18.7685C24.9162 19.2594 24.5182 19.6574 24.0273 19.6574H8.97263C8.48171 19.6574 8.08374 19.2594 8.08374 18.7685Z"
fill="white"
fill-opacity="0.65"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 21.4666C8.08374 20.9757 8.48171 20.5777 8.97263 20.5777H24.0273C24.5182 20.5777 24.9162 20.9757 24.9162 21.4666C24.9162 21.9575 24.5182 22.3555 24.0273 22.3555H8.97263C8.48171 22.3555 8.08374 21.9575 8.08374 21.4666ZM8.08374 24.1333C8.08374 23.6424 8.48171 23.2444 8.97263 23.2444H24.0273C24.5182 23.2444 24.9162 23.6424 24.9162 24.1333C24.9162 24.6242 24.5182 25.0222 24.0273 25.0222H8.97263C8.48171 25.0222 8.08374 24.6242 8.08374 24.1333ZM8.08374 26.8C8.08374 26.309 8.48171 25.9111 8.97263 25.9111H24.0273C24.5182 25.9111 24.9162 26.309 24.9162 26.8C24.9162 27.2909 24.5182 27.6888 24.0273 27.6888H8.97263C8.48171 27.6888 8.08374 27.2909 8.08374 26.8ZM8.08374 29.4666C8.08374 28.9757 8.48171 28.5777 8.97263 28.5777H24.0273C24.5182 28.5777 24.9162 28.9757 24.9162 29.4666C24.9162 29.9575 24.5182 30.3555 24.0273 30.3555H8.97263C8.48171 30.3555 8.08374 29.9575 8.08374 29.4666Z"
fill="currentColor"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.08374 21.4666C8.08374 20.9757 8.48171 20.5777 8.97263 20.5777H24.0273C24.5182 20.5777 24.9162 20.9757 24.9162 21.4666C24.9162 21.9575 24.5182 22.3555 24.0273 22.3555H8.97263C8.48171 22.3555 8.08374 21.9575 8.08374 21.4666ZM8.08374 24.1333C8.08374 23.6424 8.48171 23.2444 8.97263 23.2444H24.0273C24.5182 23.2444 24.9162 23.6424 24.9162 24.1333C24.9162 24.6242 24.5182 25.0222 24.0273 25.0222H8.97263C8.48171 25.0222 8.08374 24.6242 8.08374 24.1333ZM8.08374 26.8C8.08374 26.309 8.48171 25.9111 8.97263 25.9111H24.0273C24.5182 25.9111 24.9162 26.309 24.9162 26.8C24.9162 27.2909 24.5182 27.6888 24.0273 27.6888H8.97263C8.48171 27.6888 8.08374 27.2909 8.08374 26.8ZM8.08374 29.4666C8.08374 28.9757 8.48171 28.5777 8.97263 28.5777H24.0273C24.5182 28.5777 24.9162 28.9757 24.9162 29.4666C24.9162 29.9575 24.5182 30.3555 24.0273 30.3555H8.97263C8.48171 30.3555 8.08374 29.9575 8.08374 29.4666Z"
fill="white"
fill-opacity="0.65"
/>
<rect
x="4.57422"
y="0.5"
width="23.9258"
height="31.8206"
fill="url(#paint0_linear_3236_2952)"
fill-opacity="0.4"
/>
</g>
<rect
x="4.85"
y="0.85"
width="23.3"
height="31.3"
rx="3.20556"
stroke="currentColor"
stroke-width="0.7"
/>
<rect
x="4.85"
y="0.85"
width="23.3"
height="31.3"
rx="3.20556"
stroke="white"
stroke-opacity="0.65"
stroke-width="0.7"
/>
<rect
x="4.85"
y="0.85"
width="23.3"
height="31.3"
rx="3.20556"
stroke="url(#paint1_linear_3236_2952)"
stroke-opacity="0.23"
stroke-width="0.7"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_3236_2952"
x1="16.5371"
y1="0.5"
x2="16.5371"
y2="32.3206"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0" />
<stop offset="1" stop-color="white" />
</linearGradient>
<linearGradient
id="paint1_linear_3236_2952"
x1="16.5"
y1="0.5"
x2="16.5"
y2="32.5"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" stop-opacity="0" />
<stop offset="1" stop-color="white" />
</linearGradient>
<clipPath id="clip0_3236_2952">
<rect
width="32"
height="32"
fill="white"
transform="translate(0.5 0.5)"
/>
</clipPath>
<clipPath id="clip1_3236_2952">
<rect x="4.5" y="0.5" width="24" height="32" rx="3.55556" fill="white" />
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 683 B

After

Width:  |  Height:  |  Size: 6.2 KiB

@@ -32,7 +32,7 @@ export const SimpleDocItem = ({
showAccesses = false,
}: SimpleDocItemProps) => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const { untitledDocument } = useTrans();
@@ -52,9 +52,15 @@ export const SimpleDocItem = ({
`}
>
{isPinned ? (
<PinnedDocumentIcon aria-label={t('Pin document icon')} />
<PinnedDocumentIcon
aria-label={t('Pin document icon')}
color={colorsTokens['primary-500']}
/>
) : (
<SimpleFileIcon aria-label={t('Simple document icon')} />
<SimpleFileIcon
aria-label={t('Simple document icon')}
color={colorsTokens['primary-500']}
/>
)}
</Box>
<Box $justify="center" $overflow="auto">
@@ -1,11 +1,15 @@
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { Box, StyledLink, Text } from '@/components/';
import { useCunninghamTheme } from '@/cunningham';
import { useConfig } from '@/core/config';
import { Title } from '../header';
import IconLink from './assets/external-link.svg';
import { ContentType } from './types';
const BlueStripe = styled.div`
position: absolute;
@@ -16,9 +20,40 @@ const BlueStripe = styled.div`
`;
export const Footer = () => {
const { t } = useTranslation();
const { themeTokens } = useCunninghamTheme();
const logo = themeTokens.logo;
const { data: config } = useConfig();
const footerJson = config?.theme_customization?.footer;
const { i18n, t } = useTranslation();
const resolvedLanguage = i18n.resolvedLanguage;
const [content, setContent] = useState<ContentType>();
useEffect(() => {
if (!footerJson) {
return;
}
const langData = footerJson[resolvedLanguage as keyof typeof footerJson];
const innerContent: ContentType = {};
innerContent.logo = langData?.logo || footerJson?.default?.logo;
innerContent.legalLinks =
langData?.legalLinks || footerJson?.default?.legalLinks;
innerContent.externalLinks =
langData && 'externalLinks' in langData
? langData?.externalLinks
: footerJson?.default?.externalLinks;
innerContent.bottomInformation =
langData && 'bottomInformation' in langData
? langData?.bottomInformation
: footerJson?.default?.bottomInformation;
setContent(innerContent);
}, [footerJson, resolvedLanguage]);
const { logo, legalLinks, externalLinks, bottomInformation } = content || {};
if (!footerJson || (!legalLinks && !externalLinks && !bottomInformation)) {
return null;
}
return (
<Box $position="relative" as="footer" className="--docs--footer">
@@ -34,14 +69,30 @@ export const Footer = () => {
<Box className="--docs--footer-logo">
<Box $align="center" $gap="6rem" $direction="row">
{logo && (
<Image
priority
src={logo.src}
alt={logo.alt}
width={0}
height={0}
style={{ width: logo.widthFooter, height: 'auto' }}
/>
<Box
$align="center"
$gap="0.5rem"
$direction="row"
$position="relative"
$height="fit-content"
>
{logo?.src && (
<Image
className="c__image-system-filter"
priority
src={logo.src}
alt={logo?.alt || t('Logo')}
width={0}
height={0}
style={{ width: logo?.width || 'auto', height: 'auto' }}
/>
)}
{logo.withTitle && (
<Box $css="zoom:1.4;">
<Title />
</Box>
)}
</Box>
)}
</Box>
</Box>
@@ -54,40 +105,24 @@ export const Footer = () => {
`}
className="--docs--footer-external-links"
>
{[
{
label: 'legifrance.gouv.fr',
href: 'https://legifrance.gouv.fr/',
},
{
label: 'info.gouv.fr',
href: 'https://info.gouv.fr/',
},
{
label: 'service-public.fr',
href: 'https://service-public.fr/',
},
{
label: 'data.gouv.fr',
href: 'https://data.gouv.fr/',
},
].map(({ label, href }) => (
<StyledLink
key={label}
href={href}
target="__blank"
$css={`
gap:0.2rem;
transition: box-shadow 0.3s;
&:hover {
box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text);
}
`}
>
<Text $weight="bold">{label}</Text>
<IconLink width={18} />
</StyledLink>
))}
{externalLinks &&
externalLinks.map(({ label, href }) => (
<StyledLink
key={label}
href={href}
target="__blank"
$css={`
gap:0.2rem;
transition: box-shadow 0.3s;
&:hover {
box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text);
}
`}
>
<Text $weight="bold">{label}</Text>
<IconLink width={18} />
</StyledLink>
))}
</Box>
</Box>
<Box
@@ -102,66 +137,62 @@ export const Footer = () => {
`}
className="--docs--footer-internal-links"
>
{[
{
label: t('Legal Notice'),
href: '/legal-notice',
},
{
label: t('Personal data and cookies'),
href: '/personal-data-cookies',
},
{
label: t('Accessibility'),
href: '/accessibility',
},
].map(({ label, href }) => (
<StyledLink
key={label}
href={href}
$css={`
padding-right: 1rem;
&:not(:last-child) {
box-shadow: inset -1px 0px 0px 0px var(--c--theme--colors--greyscale-200);
}
`}
>
<Text
$variation="600"
$size="m"
$transition="box-shadow 0.3s"
$css={`
&:hover {
box-shadow: 0px 2px 0 0 var(--c--theme--colors--greyscale-text);
{legalLinks &&
legalLinks.map(({ label, href }) => (
<StyledLink
key={label}
href={href}
$css={css`
padding-right: 1rem;
&:not(:last-child) {
box-shadow: inset -1px 0px 0px 0px
var(--c--theme--colors--greyscale-200);
}
`}
>
{label}
</Text>
</StyledLink>
))}
<Text
$variation="600"
$size="m"
$transition="box-shadow 0.3s"
$css={css`
&:hover {
box-shadow: 0px 2px 0 0
var(--c--theme--colors--greyscale-text);
}
`}
>
{label}
</Text>
</StyledLink>
))}
</Box>
<Text
as="p"
$size="m"
$margin={{ top: 'big' }}
$variation="600"
$display="inline"
className="--docs--footer-licence"
>
{t('Unless otherwise stated, all content on this site is under')}{' '}
<StyledLink
href="https://github.com/etalab/licence-ouverte/blob/master/LO.md"
target="__blank"
$css={`
display:inline-flex;
box-shadow: 0px 1px 0 0 var(--c--theme--colors--greyscale-text);
`}
{bottomInformation && (
<Text
as="p"
$size="m"
$margin={{ top: 'big' }}
$variation="600"
$display="inline"
className="--docs--footer-licence"
>
<Text $variation="600">licence etalab-2.0</Text>
<IconLink width={18} />
</StyledLink>
</Text>
{bottomInformation.label}{' '}
{bottomInformation.link && (
<StyledLink
href={bottomInformation.link.href}
target="__blank"
$css={css`
display: inline-flex;
box-shadow: 0px 1px 0 0
var(--c--theme--colors--greyscale-text);
gap: 0.2rem;
`}
>
<Text $variation="600">{bottomInformation.link.label}</Text>
<IconLink width={14} />
</StyledLink>
)}
</Text>
)}
</Box>
</Box>
);
@@ -1 +1,2 @@
export * from './Footer';
export * from './types';
@@ -0,0 +1,28 @@
export interface FooterType {
default: ContentType;
[key: string]: ContentType;
}
export interface BottomInformation {
label: string;
link?: Link;
}
export interface Link {
label: string;
href: string;
}
export interface Logo {
src: string;
width: string;
alt: string;
withTitle: boolean;
}
export interface ContentType {
logo?: Logo;
externalLinks?: Link[];
legalLinks?: Link[];
bottomInformation?: BottomInformation;
}
@@ -1,61 +0,0 @@
<svg
width="60"
height="60"
viewBox="0 0 60 60"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
opacity="0.1"
x="1"
y="1"
width="58"
height="58"
rx="1"
fill="white"
stroke="#000091"
stroke-width="2"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M29.4103 24.6079L34.1507 27.3446V32.8178L29.4103 35.5544L24.6699 32.8178V27.3446L29.4103 24.6079Z"
fill="#000091"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M34.7463 15L39.4867 17.7367V23.2099L34.7463 25.9465L30.0059 23.2099V17.7367L34.7463 15Z"
fill="#000091"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M34.7463 34.0537L39.4867 36.7904V42.2636L34.7463 45.0002L30.0059 42.2636V36.7904L34.7463 34.0537Z"
fill="#000091"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M18.7404 24.6079L23.4808 27.3446V32.8178L18.7404 35.5544L14 32.8178V27.3446L18.7404 24.6079Z"
fill="#000091"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M24.0744 15L28.8148 17.7367V23.2099L24.0744 25.9465L19.334 23.2099V17.7367L24.0744 15Z"
fill="#000091"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M24.0744 34.0537L28.8148 36.7904V42.2636L24.0744 45.0002L19.334 42.2636V36.7904L24.0744 34.0537Z"
fill="#000091"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M40.2717 24.6079L45.0121 27.3446V32.8178L40.2717 35.5544L35.5312 32.8178V27.3446L40.2717 24.6079Z"
fill="#000091"
/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -48,7 +48,11 @@ export const Header = () => {
$height="fit-content"
$margin={{ top: 'auto' }}
>
<IconDocs aria-label={t('Docs Logo')} width={32} />
<IconDocs
aria-label={t('Docs Logo')}
width={32}
color={colorsTokens['primary-text']}
/>
<Title />
</Box>
</StyledLink>
@@ -15,7 +15,7 @@ const GaufreStyle = createGlobalStyle`
export const LaGaufre = () => {
const { componentTokens } = useCunninghamTheme();
if (!componentTokens['la-gauffre'].activated) {
if (!componentTokens['la-gaufre']) {
return null;
}
@@ -6,7 +6,9 @@ import { useCunninghamTheme } from '@/cunningham';
export const Title = () => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const { spacingsTokens, colorsTokens, componentTokens } =
useCunninghamTheme();
const isBeta = componentTokens['beta'];
return (
<Box
@@ -18,32 +20,34 @@ export const Title = () => {
<Text
$margin="none"
as="h2"
$color="#000091"
$color={colorsTokens['primary-text']}
$zIndex={1}
$size="1.375rem"
>
{t('Docs')}
</Text>
<Text
$padding={{
horizontal: '6px',
vertical: '4px',
}}
$size="11px"
$theme="primary"
$variation="500"
$weight="bold"
$radius="12px"
$css={css`
line-height: 9px;
`}
$width="40px"
$height="16px"
$background="#ECECFF"
$color="#5958D3"
>
BETA
</Text>
{isBeta && (
<Text
$padding={{
horizontal: '6px',
vertical: '4px',
}}
$size="11px"
$theme="primary"
$variation="500"
$weight="bold"
$radius="12px"
$css={css`
line-height: 9px;
`}
$width="40px"
$height="16px"
$background="#ECECFF"
$color="#5958D3"
>
BETA
</Text>
)}
</Box>
);
};
@@ -3,7 +3,7 @@ import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import DocLogo from '@/assets/icons/icon-docs.svg?url';
import IconDocs from '@/assets/icons/icon-docs.svg';
import { Box, Icon, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { ProConnectButton, gotoLogin } from '@/features/auth';
@@ -15,9 +15,10 @@ import { getHeaderHeight } from './HomeHeader';
export default function HomeBanner() {
const { t } = useTranslation();
const { componentTokens, spacingsTokens } = useCunninghamTheme();
const { componentTokens, spacingsTokens, colorsTokens } =
useCunninghamTheme();
const { isMobile, isSmallMobile } = useResponsiveStore();
const withProConnect = componentTokens['home-proconnect'].activated;
const withProConnect = componentTokens['home-proconnect'];
return (
<Box
@@ -46,7 +47,11 @@ export default function HomeBanner() {
$align="center"
$gap={spacingsTokens['sm']}
>
<Image src={DocLogo} alt="DocLogo" width={64} />
<IconDocs
aria-label={t('Docs Logo')}
width={64}
color={colorsTokens['primary-text']}
/>
<Text
as="h2"
$size={!isMobile ? 'xs-alt' : '2.3rem'}
@@ -83,6 +88,7 @@ export default function HomeBanner() {
</Box>
{!isMobile && (
<Image
className="c__image-system-filter"
src={banner}
alt={t('Banner image')}
priority
@@ -1,7 +1,6 @@
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import DocLogo from '@/assets/icons/icon-docs.svg?url';
import IconDocs from '@/assets/icons/icon-docs.svg';
import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { ProConnectButton } from '@/features/auth';
@@ -10,7 +9,7 @@ import { useResponsiveStore } from '@/stores';
export function HomeBottom() {
const { componentTokens } = useCunninghamTheme();
const withProConnect = componentTokens['home-proconnect'].activated;
const withProConnect = componentTokens['home-proconnect'];
if (!withProConnect) {
return null;
@@ -21,7 +20,7 @@ export function HomeBottom() {
function HomeProConnect() {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const { isMobile } = useResponsiveStore();
const parentGap = '230px';
@@ -45,7 +44,11 @@ function HomeProConnect() {
$height="fit-content"
$css="zoom: 1.9;"
>
<Image src={DocLogo} alt="DocLogo" />
<IconDocs
aria-label={t('Docs Logo')}
width={34}
color={colorsTokens['primary-text']}
/>
<Title />
</Box>
<Text $size="md" $variation="1000" $textAlign="center">
@@ -61,7 +61,7 @@ export function HomeContent() {
$gap={isMobile ? '115px' : '230px'}
$padding={{ bottom: '3rem' }}
>
<Box $gap="30px">
<Box $gap={isMobile ? '115px' : '30px'}>
<HomeSection
isColumn={false}
isSmallDevice={isTablet}
@@ -17,7 +17,7 @@ export const getHeaderHeight = (isSmallMobile: boolean) =>
export const HomeHeader = () => {
const { t } = useTranslation();
const { themeTokens, spacingsTokens } = useCunninghamTheme();
const { themeTokens, spacingsTokens, colorsTokens } = useCunninghamTheme();
const logo = themeTokens.logo;
const { isSmallMobile } = useResponsiveStore();
@@ -44,7 +44,7 @@ export const HomeHeader = () => {
<ButtonTogglePanel />
</Box>
)}
{!isSmallMobile && logo && (
{!isSmallMobile && logo?.src && (
<Image
priority
src={logo.src}
@@ -61,7 +61,11 @@ export const HomeHeader = () => {
$position="relative"
$height="fit-content"
>
<IconDocs aria-label={t('Docs Logo')} width={32} />
<IconDocs
aria-label={t('Docs Logo')}
width={32}
color={colorsTokens['primary-text']}
/>
<Title />
</Box>
</Box>
@@ -174,6 +174,7 @@ export const HomeSection = ({
{illustration && (isSmallDevice || !video) && (
<Image
className="c__image-system-filter"
src={illustration}
alt={t('Illustration')}
style={{
@@ -108,10 +108,9 @@ const FALLBACK = {
const precacheResources = [
'/',
'/index.html',
'/401/',
'/404/',
'/accessibility/',
'/legal-notice/',
'/personal-data-cookies/',
'/403/',
FALLBACK.offline,
FALLBACK.images,
FALLBACK.docs,
File diff suppressed because it is too large Load Diff
@@ -29,6 +29,7 @@ const Page: NextPageWithLayout = () => {
$padding={{ bottom: '2rem' }}
>
<Image
className="c__image-system-filter"
src={img401}
alt={t('Image 401')}
style={{
@@ -24,6 +24,7 @@ const Page: NextPageWithLayout = () => {
$padding={{ bottom: '2rem' }}
>
<Image
className="c__image-system-filter"
src={img403}
alt={t('Image 403')}
style={{
+23 -15
View File
@@ -1,37 +1,45 @@
import { Button } from '@openfun/cunningham-react';
import Image from 'next/image';
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import styled from 'styled-components';
import Icon404 from '@/assets/icons/icon-404.svg';
import img403 from '@/assets/icons/icon-403.png';
import { Box, Icon, StyledLink, Text } from '@/components';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const StyledButton = styled(Button)`
width: fit-content;
padding-left: 2rem;
padding-right: 2rem;
`;
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
return (
<Box $align="center" $margin="auto" $height="70vh" $gap="2rem">
<Icon404 aria-label="Image 404" role="img" />
<Box
$align="center"
$margin="auto"
$gap="1rem"
$padding={{ bottom: '2rem' }}
>
<Image
className="c__image-system-filter"
src={img403}
alt={t('Image 403')}
style={{
maxWidth: '100%',
height: 'auto',
}}
/>
<Text $size="h2" $weight="700" $theme="greyscale" $variation="900">
{t('Ouch !')}
</Text>
<Box $align="center" $gap="0.8rem">
<Text as="p" $textAlign="center" $maxWidth="350px" $theme="primary">
{t(
'It seems that the page you are looking for does not exist or cannot be displayed correctly.',
)}
</Text>
<Text as="p" $textAlign="center" $maxWidth="400px" $size="m">
{t(
'It seems that the page you are looking for does not exist or cannot be displayed correctly.',
)}
</Text>
<Box $margin={{ top: 'large' }}>
<StyledLink href="/">
<StyledButton icon={<Icon iconName="house" $color="white" />}>
{t('Home')}
+7 -4
View File
@@ -3,6 +3,7 @@ import Head from 'next/head';
import { useTranslation } from 'react-i18next';
import { AppProvider } from '@/core/';
import { useCunninghamTheme } from '@/cunningham';
import { useOffline, useSWRegister } from '@/features/service-worker/';
import '@/i18n/initI18n';
import { NextPageWithLayout } from '@/types/next';
@@ -18,6 +19,8 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) {
useOffline();
const getLayout = Component.getLayout ?? ((page) => page);
const { t } = useTranslation();
const { componentTokens } = useCunninghamTheme();
const favicon = componentTokens['favicon'];
return (
<>
@@ -29,17 +32,17 @@ export default function App({ Component, pageProps }: AppPropsWithLayout) {
'Docs: Your new companion to collaborate on documents efficiently, intuitively, and securely.',
)}
/>
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/favicon.png" type="image/png" />
<link rel="icon" href={favicon['ico']} sizes="any" />
<link rel="icon" href={favicon['png-light']} type="image/png" />
<link
rel="icon"
href="/favicon.png"
href={favicon['png-light']}
type="image/png"
media="(prefers-color-scheme: light)"
/>
<link
rel="icon"
href="/favicon-dark.png"
href={favicon['png-dark']}
type="image/png"
media="(prefers-color-scheme: dark)"
/>
@@ -1,133 +0,0 @@
import { ReactElement } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Box, Text, TextStyled } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
return (
<Box>
<Box
as="h1"
$background={colorsTokens['primary-100']}
$margin="none"
$padding="large"
>
{t('Accessibility')}
</Box>
<Box $padding={{ horizontal: 'large', vertical: 'big' }}>
<Text as="p" $display="inline">
<Trans t={t} i18nKey="accessibility-dinum-services">
<strong>DINUM</strong> is committed to making its digital services
accessible, in accordance with article 47 of French law n° 2005-102
dated February 11, 2005.
</Trans>
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Accessibility statement')}
</Text>
<Text as="p">{t('Established on December 20, 2023.')}</Text>
<Text as="p" $display="inline">
{t('This accessibility statement applies to the site hosted on')}{' '}
<strong>https://docs.numerique.gouv.fr</strong>.
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Compliance status')}
</Text>
<Text as="p" $display="inline">
<Trans t={t} i18nKey="accessibility-not-audit">
<strong>https://docs.numerique.gouv.fr</strong> is not compliant
with RGAA 4.1. The site has <strong>not yet been audited.</strong>
</Trans>
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Improvement and contact')}
</Text>
<Text as="p" $display="inline">
{t(
'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.',
)}
</Text>
<Text as="p" $display="inline">
<li>
{t('E-mail:')}{' '}
<TextStyled
as="a"
href="mailto:lasuite@modernisation.gouv.fr"
$display="inline"
>
lasuite@modernisation.gouv.fr
</TextStyled>
</li>
<li>
{t('Address:')} <strong>DINUM</strong>, 20 avenue de Ségur 75007
Paris
</li>
</Text>
<Text as="p" $display="inline">
{t('We try to respond within 2 working days.')}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Remedies')}
</Text>
<Text as="p" $display="inline">
{t('This procedure should be used in the following case:')}{' '}
{t(
'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.',
)}
</Text>
<Text as="p" $display="inline" $margin={{ bottom: 'tiny' }}>
{t('You can:')}
</Text>
<Text as="p" $display="inline" $margin={{ top: 'tiny' }}>
<li>
<Trans t={t} i18nKey="accessibility-form-defenseurdesdroits">
Write a message to the
<TextStyled
as="a"
href="https://formulaire.defenseurdesdroits.fr/formulaire_saisine/"
$display="inline"
$margin={{ left: '4px' }}
>
Defender of Rights
</TextStyled>
</Trans>
</li>
<li>
<Trans t={t} i18nKey="accessibility-contact-defenseurdesdroits">
Contact the delegate of the
<TextStyled
as="a"
href="https://www.defenseurdesdroits.fr/carte-des-delegues"
$display="inline"
$margin={{ left: '4px' }}
>
Defender of Rights in your region
</TextStyled>
</Trans>
</li>
<li>
{t('Send a letter by post (free of charge, no stamp needed):')}{' '}
<strong>
{t(
'Defender of Rights - Free response - 71120 75342 Paris CEDEX 07',
)}
</strong>
</li>
</Text>
</Box>
</Box>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PageLayout>{page}</PageLayout>;
};
export default Page;
@@ -1,72 +0,0 @@
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text, TextStyled } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
return (
<Box>
<Box
as="h1"
$background={colorsTokens['primary-100']}
$margin="none"
$padding="large"
>
{t('Legal notice')}
</Box>
<Box $padding={{ horizontal: 'large', vertical: 'big' }}>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Publisher')}
</Text>
<Text as="p">
{t(
'French Interministerial Directorate for Digital Affairs (DINUM), 20 avenue de Ségur 75007 Paris.',
)}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Publication Director')}
</Text>
<Text as="p">
{t('Stéphanie Schaer: Interministerial Digital Director (DINUM).')}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Copyright')}
</Text>
<Text as="p" $display="inline">
{t('Illustration:')}{' '}
<Text $weight="bold" $display="inline">
DINUM
</Text>
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('More info?')}
</Text>
<Text as="p" $display="inline">
{t(
'The team in charge of the digital workspace "La Suite numérique" can be contacted directly at',
)}{' '}
<TextStyled
as="a"
href="mailto:lasuite@modernisation.gouv.fr"
$display="inline"
>
lasuite@modernisation.gouv.fr
</TextStyled>
.
</Text>
</Box>
</Box>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PageLayout>{page}</PageLayout>;
};
export default Page;
@@ -1,66 +0,0 @@
import { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { PageLayout } from '@/layouts';
import { NextPageWithLayout } from '@/types/next';
const Page: NextPageWithLayout = () => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
return (
<Box>
<Box
as="h1"
$background={colorsTokens['primary-100']}
$margin="none"
$padding="large"
>
{t('Personal data and cookies')}
</Box>
<Box $padding={{ horizontal: 'large', vertical: 'big' }}>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('Cookies placed')}
</Text>
<Text as="p">
{t(
'This site places a small text file (a "cookie") on your computer when you visit it.',
)}
{t(
'This allows us to measure the number of visits and understand which pages are the most viewed.',
)}
</Text>
<Text as="p">
{t('You can oppose the tracking of your browsing on this website.')}
{t(
'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.',
)}
</Text>
<Text as="h2" $margin={{ bottom: 'xtiny' }}>
{t('This site does not display a cookie consent banner, why?')}
</Text>
<Text as="p">
{t(
"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!",
)}
</Text>
<Text as="p">
{t(
'Nothing exceptional, no special privileges related to a .gouv.fr.',
)}
{t(
'We simply comply with the law, which states that certain audience measurement tools, properly configured to respect privacy, are exempt from prior authorization.',
)}
</Text>
</Box>
</Box>
);
};
Page.getLayout = function getLayout(page: ReactElement) {
return <PageLayout>{page}</PageLayout>;
};
export default Page;
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "impress",
"version": "3.2.1",
"version": "3.3.0",
"private": true,
"workspaces": {
"packages": [
@@ -28,8 +28,8 @@
"server:test": "yarn COLLABORATION_SERVER run test"
},
"resolutions": {
"@types/node": "22.15.19",
"@types/react": "19.1.4",
"@types/node": "22.15.21",
"@types/react": "19.1.5",
"@types/react-dom": "19.1.5",
"@typescript-eslint/eslint-plugin": "8.32.1",
"@typescript-eslint/parser": "8.32.1",
@@ -13,6 +13,10 @@ const eslintTS = [
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{ varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
],
'sort-imports': [
'error',
{
@@ -1,6 +1,6 @@
{
"name": "eslint-config-impress",
"version": "3.2.1",
"version": "3.3.0",
"license": "MIT",
"scripts": {
"lint": "eslint --ext .js ."

Some files were not shown because too many files have changed in this diff Show More