Compare commits

..

1 Commits

Author SHA1 Message Date
Anthony LC 4eb8c23fd3 🔖(minor) release 3.6.0
Added:
- 👷(CI) add bundle size check job
- (frontend) use title first emoji as doc icon in tree

Changed:
- ♻️(docs-app) Switch from Jest tests to Vitest
- (frontend) improve accessibility:
  - 🌐(frontend) set html lang attribute dynamically
  - (frontend) inject language attribute to pdf export
  - (frontend) improve accessibility of search modal
  - (frontend) add correct attributes to decorative and interactive icons
  - 🎨(frontend) improve nav structure
  - ️(frontend) keyboard interaction with menu
  - (frontend) improve header accessibility
  - (frontend) improve accessibility for decorative images in editor
- ♻️(backend) fallback to email identifier when no name
- 🐛(backend) allow ASCII characters in user sub field
- ️(frontend) improve fallback width calculation

Fixed:
- 🐛(makefile) Windows compatibility fix for Docker volume mounting
- 🐛(minio) fix user permission error with Minio and Windows
- 🐛(frontend) fix export when quote block and inline code
- 🐛(frontend) fix base64 font
- 🐛(backend) allow editor to delete subpages
- 🐛(frontend) fix dnd conflict with tree and Blocknote
- 🐛(frontend) fix display bug on homepage
2025-09-04 13:56:23 +02:00
166 changed files with 2191 additions and 3390 deletions
+2 -8
View File
@@ -32,10 +32,7 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
run: echo "${{ secrets.DOCKER_HUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_HUB_USER }}" --password-stdin
-
name: Run trivy scan
uses: numerique-gouv/action-trivy-cache@main
@@ -68,10 +65,7 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
run: echo "${{ secrets.DOCKER_HUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_HUB_USER }}" --password-stdin
-
name: Run trivy scan
uses: numerique-gouv/action-trivy-cache@main
+2 -34
View File
@@ -8,34 +8,6 @@ and this project adheres to
## [Unreleased]
## [3.7.0] - 2025-09-12
### Added
- ✨(api) add API route to fetch document content #1206
### Changed
- 🔒️(backend) configure throttle on every viewsets #1343
- ⬆️ Bump eslint to V9 #1071
- ♿(frontend) improve accessibility:
- ♿fix major accessibility issues reported by wave and axe #1344
- ✨unify tab focus style for better visual consistency #1341
- ✨improve modal a11y: structure, labels, and title #1349
- ✨improve accessibility of cdoc content with correct aria tags #1271
- ✨unify tab focus style for better visual consistency #1341
- ♿hide decorative icons, label menus, avoid accessible name… #1362
- ♻️(tilt) use helm dev-backend chart
### Removed
- 🔥(frontend) remove multi column drop cursor #1370
### Fixed
- 🐛(frontend) fix callout emoji list #1366
## [3.6.0] - 2025-09-04
### Added
@@ -55,10 +27,8 @@ and this project adheres to
- ♿️(frontend) keyboard interaction with menu #1244
- ♿(frontend) improve header accessibility #1270
- ♿(frontend) improve accessibility for decorative images in editor #1282
- #1338
- #1281
- ♻️(backend) fallback to email identifier when no name #1298
- 🐛(backend) allow ASCII characters in user sub field #1295
- 🐛(backend) allow ASCII characters in user sub field #1295
- ⚡️(frontend) improve fallback width calculation #1333
### Fixed
@@ -70,7 +40,6 @@ and this project adheres to
- 🐛(backend) allow creator to delete subpages #1297
- 🐛(frontend) fix dnd conflict with tree and Blocknote #1328
- 🐛(frontend) fix display bug on homepage #1332
- 🐛link role update #1287
## [3.5.0] - 2025-07-31
@@ -742,8 +711,7 @@ and this project adheres to
- ✨(frontend) Coming Soon page (#67)
- 🚀 Impress, project to manage your documents easily and collaboratively.
[unreleased]: https://github.com/suitenumerique/docs/compare/v3.7.0...main
[v3.7.0]: https://github.com/suitenumerique/docs/releases/v3.7.0
[unreleased]: https://github.com/suitenumerique/docs/compare/v3.6.0...main
[v3.6.0]: https://github.com/suitenumerique/docs/releases/v3.6.0
[v3.5.0]: https://github.com/suitenumerique/docs/releases/v3.5.0
[v3.4.2]: https://github.com/suitenumerique/docs/releases/v3.4.2
+1 -1
View File
@@ -440,6 +440,6 @@ bump-packages-version: ## bump the version of the project - VERSION_TYPE can be
cd ./src/frontend/apps/e2e/ && yarn version --no-git-tag-version --$(VERSION_TYPE)
cd ./src/frontend/apps/impress/ && yarn version --no-git-tag-version --$(VERSION_TYPE)
cd ./src/frontend/servers/y-provider/ && yarn version --no-git-tag-version --$(VERSION_TYPE)
cd ./src/frontend/packages/eslint-plugin-docs/ && yarn version --no-git-tag-version --$(VERSION_TYPE)
cd ./src/frontend/packages/eslint-config-impress/ && yarn version --no-git-tag-version --$(VERSION_TYPE)
cd ./src/frontend/packages/i18n/ && yarn version --no-git-tag-version --$(VERSION_TYPE)
.PHONY: bump-packages-version
+2 -3
View File
@@ -39,10 +39,9 @@ docker_build(
]
)
k8s_resource('impress-docs-backend-migrate', resource_deps=['dev-backend-postgres'])
k8s_resource('impress-docs-backend-migrate', resource_deps=['postgres-postgresql'])
k8s_resource('impress-docs-backend-createsuperuser', resource_deps=['impress-docs-backend-migrate'])
k8s_resource('dev-backend-keycloak', resource_deps=['dev-backend-keycloak-pg'])
k8s_resource('impress-docs-backend', resource_deps=['impress-docs-backend-migrate', 'dev-backend-redis', 'dev-backend-keycloak', 'dev-backend-postgres', 'dev-backend-minio:statefulset'])
k8s_resource('impress-docs-backend', resource_deps=['impress-docs-backend-migrate'])
k8s_yaml(local('cd ../src/helm && helmfile -n impress -e dev template .'))
migration = '''
-4
View File
@@ -3,7 +3,3 @@ BURST_THROTTLE_RATES="200/minute"
COLLABORATION_API_URL=http://y-provider:4444/collaboration/api/
SUSTAINED_THROTTLE_RATES="200/hour"
Y_PROVIDER_API_BASE_URL=http://y-provider:4444/api/
# Throttle
API_DOCUMENT_THROTTLE_RATE=1000/min
API_CONFIG_THROTTLE_RATE=1000/min
+1
View File
@@ -27,6 +27,7 @@
"@hocuspocus/provider",
"@hocuspocus/server",
"docx",
"eslint",
"fetch-mock",
"node",
"node-fetch",
-8
View File
@@ -128,11 +128,3 @@ class ListDocumentFilter(DocumentFilter):
queryset_method = queryset.filter if bool(value) else queryset.exclude
return queryset_method(link_traces__user=user, link_traces__is_masked=True)
class UserSearchFilter(django_filters.FilterSet):
"""
Custom filter for searching users.
"""
q = django_filters.CharFilter(min_length=5, max_length=254)
-21
View File
@@ -1,21 +0,0 @@
"""Throttling modules for the API."""
from rest_framework.throttling import UserRateThrottle
from sentry_sdk import capture_message
def sentry_monitoring_throttle_failure(message):
"""Log when a failure occurs to detect rate limiting issues."""
capture_message(message, "warning")
class UserListThrottleBurst(UserRateThrottle):
"""Throttle for the user list endpoint."""
scope = "user_list_burst"
class UserListThrottleSustained(UserRateThrottle):
"""Throttle for the user list endpoint."""
scope = "user_list_sustained"
+16 -90
View File
@@ -1,7 +1,6 @@
"""API endpoints"""
# pylint: disable=too-many-lines
import base64
import json
import logging
import uuid
@@ -34,25 +33,16 @@ from lasuite.malware_detection import malware_detection
from rest_framework import filters, status, viewsets
from rest_framework import response as drf_response
from rest_framework.permissions import AllowAny
from rest_framework.throttling import UserRateThrottle
from core import authentication, choices, enums, models
from core.services.ai_services import AIService
from core.services.collaboration_services import CollaborationService
from core.services.converter_services import (
ServiceUnavailableError as YProviderServiceUnavailableError,
)
from core.services.converter_services import (
ValidationError as YProviderValidationError,
)
from core.services.converter_services import (
YdocConverter,
)
from core.tasks.mail import send_ask_for_access_mail
from core.utils import extract_attachments, filter_descendants
from . import permissions, serializers, utils
from .filters import DocumentFilter, ListDocumentFilter, UserSearchFilter
from .throttling import UserListThrottleBurst, UserListThrottleSustained
from .filters import DocumentFilter, ListDocumentFilter
logger = logging.getLogger(__name__)
@@ -146,6 +136,18 @@ class Pagination(drf.pagination.PageNumberPagination):
page_size_query_param = "page_size"
class UserListThrottleBurst(UserRateThrottle):
"""Throttle for the user list endpoint."""
scope = "user_list_burst"
class UserListThrottleSustained(UserRateThrottle):
"""Throttle for the user list endpoint."""
scope = "user_list_sustained"
class UserViewSet(
drf.mixins.UpdateModelMixin, viewsets.GenericViewSet, drf.mixins.ListModelMixin
):
@@ -176,18 +178,12 @@ class UserViewSet(
if self.action != "list":
return queryset
filterset = UserSearchFilter(
self.request.GET, queryset=queryset, request=self.request
)
if not filterset.is_valid():
raise drf.exceptions.ValidationError(filterset.errors)
# Exclude all users already in the given document
if document_id := self.request.query_params.get("document_id", ""):
queryset = queryset.exclude(documentaccess__document_id=document_id)
filter_data = filterset.form.cleaned_data
query = filter_data["q"]
if not (query := self.request.query_params.get("q", "")) or len(query) < 5:
return queryset.none()
# For emails, match emails by Levenstein distance to prevent typing errors
if "@" in query:
@@ -364,7 +360,6 @@ class DocumentViewSet(
permission_classes = [
permissions.DocumentPermission,
]
throttle_scope = "document"
queryset = models.Document.objects.select_related("creator").all()
serializer_class = serializers.DocumentSerializer
ai_translate_serializer_class = serializers.AITranslateSerializer
@@ -1510,69 +1505,6 @@ class DocumentViewSet(
status=status.HTTP_400_BAD_REQUEST,
)
@drf.decorators.action(
detail=True,
methods=["get"],
url_path="content",
name="Get document content in different formats",
)
def content(self, request, pk=None):
"""
Retrieve document content in different formats (JSON, Markdown, HTML).
Query parameters:
- content_format: The desired output format (json, markdown, html)
Returns:
JSON response with content in the specified format.
"""
document = self.get_object()
content_format = request.query_params.get("content_format", "json").lower()
if content_format not in {"json", "markdown", "html"}:
raise drf.exceptions.ValidationError(
"Invalid format. Must be one of: json, markdown, html"
)
# Get the base64 content from the document
content = None
base64_content = document.content
if base64_content is not None:
# Convert using the y-provider service
try:
yprovider = YdocConverter()
result = yprovider.convert(
base64.b64decode(base64_content),
"application/vnd.yjs.doc",
{
"markdown": "text/markdown",
"html": "text/html",
"json": "application/json",
}[content_format],
)
content = result
except YProviderValidationError as e:
return drf_response.Response(
{"error": str(e)}, status=status.HTTP_400_BAD_REQUEST
)
except YProviderServiceUnavailableError as e:
logger.error("Error getting content for document %s: %s", pk, e)
return drf_response.Response(
{"error": "Failed to get document content"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return drf_response.Response(
{
"id": str(document.id),
"title": document.title,
"content": content,
"created_at": document.created_at,
"updated_at": document.updated_at,
}
)
class DocumentAccessViewSet(
ResourceAccessViewsetMixin,
@@ -1623,7 +1555,6 @@ class DocumentAccessViewSet(
"document__depth",
)
resource_field_name = "document"
throttle_scope = "document_access"
@cached_property
def document(self):
@@ -1783,7 +1714,6 @@ class TemplateViewSet(
permissions.IsAuthenticatedOrSafe,
permissions.ResourceWithAccessPermission,
]
throttle_scope = "template"
ordering = ["-created_at"]
ordering_fields = ["created_at", "updated_at", "title"]
serializer_class = serializers.TemplateSerializer
@@ -1874,7 +1804,6 @@ class TemplateAccessViewSet(
lookup_field = "pk"
permission_classes = [permissions.ResourceAccessPermission]
throttle_scope = "template_access"
queryset = models.TemplateAccess.objects.select_related("user").all()
resource_field_name = "template"
serializer_class = serializers.TemplateAccessSerializer
@@ -1957,7 +1886,6 @@ class InvitationViewset(
permissions.CanCreateInvitationPermission,
permissions.ResourceWithAccessPermission,
]
throttle_scope = "invitation"
queryset = (
models.Invitation.objects.all()
.select_related("document")
@@ -2036,7 +1964,6 @@ class DocumentAskForAccessViewSet(
permissions.IsAuthenticated,
permissions.ResourceWithAccessPermission,
]
throttle_scope = "document_ask_for_access"
queryset = models.DocumentAskForAccess.objects.all()
serializer_class = serializers.DocumentAskForAccessSerializer
_document = None
@@ -2109,7 +2036,6 @@ class ConfigView(drf.views.APIView):
"""API ViewSet for sharing some public settings."""
permission_classes = [AllowAny]
throttle_scope = "config"
def get(self, request):
"""
-1
View File
@@ -783,7 +783,6 @@ class Document(MP_Node, BaseModel):
"children_list": can_get,
"children_create": can_create_children,
"collaboration_auth": can_get,
"content": can_get,
"cors_proxy": can_get,
"descendants": can_get,
"destroy": can_destroy,
+12 -31
View File
@@ -1,4 +1,4 @@
"""Y-Provider API services."""
"""Converter services."""
from base64 import b64encode
@@ -28,44 +28,25 @@ class YdocConverter:
# Note: Yprovider microservice accepts only raw token, which is not recommended
return f"Bearer {settings.Y_PROVIDER_API_KEY}"
def _request(self, url, data, content_type, accept):
"""Make a request to the Y-Provider API."""
response = requests.post(
url,
data=data,
headers={
"Authorization": self.auth_header,
"Content-Type": content_type,
"Accept": accept,
},
timeout=settings.CONVERSION_API_TIMEOUT,
verify=settings.CONVERSION_API_SECURE,
)
response.raise_for_status()
return response
def convert(
self, text, content_type="text/markdown", accept="application/vnd.yjs.doc"
):
def convert(self, text):
"""Convert a Markdown text into our internal format using an external microservice."""
if not text:
raise ValidationError("Input text cannot be empty")
try:
response = self._request(
response = requests.post(
f"{settings.Y_PROVIDER_API_BASE_URL}{settings.CONVERSION_API_ENDPOINT}/",
text,
content_type,
accept,
data=text,
headers={
"Authorization": self.auth_header,
"Content-Type": "text/markdown",
},
timeout=settings.CONVERSION_API_TIMEOUT,
verify=settings.CONVERSION_API_SECURE,
)
if accept == "application/vnd.yjs.doc":
return b64encode(response.content).decode("utf-8")
if accept in {"text/markdown", "text/html"}:
return response.text
if accept == "application/json":
return response.json()
raise ValidationError("Unsupported format")
response.raise_for_status()
return b64encode(response.content).decode("utf-8")
except requests.RequestException as err:
raise ServiceUnavailableError(
"Failed to connect to conversion service",
@@ -4,7 +4,6 @@ Test document accesses API endpoints for users in impress's core app.
# pylint: disable=too-many-lines
import random
from unittest import mock
from uuid import uuid4
import pytest
@@ -1345,24 +1344,3 @@ def test_api_document_accesses_delete_owners_last_owner_child_team(
assert response.status_code == 204
assert models.DocumentAccess.objects.count() == 1
def test_api_document_accesses_throttling(settings):
"""Test api document accesses throttling."""
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["document_access"] = "2/minute"
user = factories.UserFactory()
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
client = APIClient()
client.force_login(user)
for _i in range(2):
response = client.get(f"/api/v1.0/documents/{document.id!s}/accesses/")
assert response.status_code == 200
with mock.patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get(f"/api/v1.0/documents/{document.id!s}/accesses/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope document_access", "warning"
)
@@ -824,29 +824,3 @@ def test_api_document_invitations_delete_readers_or_editors(via, role, mock_user
response.json()["detail"]
== "You do not have permission to perform this action."
)
def test_api_document_invitations_throttling(settings):
"""Test api document ask for access throttling."""
current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["invitation"]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["invitation"] = "2/minute"
user = factories.UserFactory()
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
factories.InvitationFactory(document=document, issuer=user)
client = APIClient()
client.force_login(user)
for _i in range(2):
response = client.get(f"/api/v1.0/documents/{document.id}/invitations/")
assert response.status_code == 200
with mock.patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get(f"/api/v1.0/documents/{document.id}/invitations/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope invitation", "warning"
)
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["invitation"] = current_rate
@@ -1,7 +1,6 @@
"""Test API for document ask for access."""
import uuid
from unittest import mock
from django.core import mail
@@ -769,35 +768,3 @@ def test_api_documents_ask_for_access_accept_authenticated_non_root_document(rol
f"/api/v1.0/documents/{child.id}/ask-for-access/{document_ask_for_access.id}/accept/"
)
assert response.status_code == 404
def test_api_document_ask_for_access_throttling(settings):
"""Test api document ask for access throttling."""
current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"][
"document_ask_for_access"
]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["document_ask_for_access"] = (
"2/minute"
)
document = DocumentFactory()
DocumentAskForAccessFactory.create_batch(
3, document=document, role=RoleChoices.READER
)
user = UserFactory()
client = APIClient()
client.force_login(user)
for _i in range(2):
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
assert response.status_code == 200
with mock.patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get(f"/api/v1.0/documents/{document.id}/ask-for-access/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope document_ask_for_access", "warning"
)
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["document_ask_for_access"] = (
current_rate
)
@@ -1,176 +0,0 @@
"""
Tests for Documents API endpoint in impress's core app: content
"""
import base64
from unittest.mock import patch
import pytest
import requests
from rest_framework import status
from rest_framework.test import APIClient
from core import factories
pytestmark = pytest.mark.django_db
@pytest.mark.parametrize(
"reach, role",
[
("public", "reader"),
("public", "editor"),
],
)
@patch("core.services.converter_services.YdocConverter.convert")
def test_api_documents_content_public(mock_content, reach, role):
"""Anonymous users should be allowed to access content of public documents."""
document = factories.DocumentFactory(link_reach=reach, link_role=role)
mock_content.return_value = {"some": "data"}
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/content/")
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["id"] == str(document.id)
assert data["title"] == document.title
assert data["content"] == {"some": "data"}
mock_content.assert_called_once_with(
base64.b64decode(document.content),
"application/vnd.yjs.doc",
"application/json",
)
@pytest.mark.parametrize(
"reach, doc_role, user_role",
[
("restricted", "reader", "reader"),
("restricted", "reader", "editor"),
("restricted", "reader", "administrator"),
("restricted", "reader", "owner"),
("restricted", "editor", "reader"),
("restricted", "editor", "editor"),
("restricted", "editor", "administrator"),
("restricted", "editor", "owner"),
("authenticated", "reader", None),
("authenticated", "editor", None),
],
)
@patch("core.services.converter_services.YdocConverter.convert")
def test_api_documents_content_not_public(mock_content, reach, doc_role, user_role):
"""Authenticated users need access to get non-public document content."""
user = factories.UserFactory()
document = factories.DocumentFactory(link_reach=reach, link_role=doc_role)
mock_content.return_value = {"some": "data"}
# First anonymous request should fail
client = APIClient()
response = client.get(f"/api/v1.0/documents/{document.id!s}/content/")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
mock_content.assert_not_called()
# Login and try again
client.force_login(user)
response = client.get(f"/api/v1.0/documents/{document.id!s}/content/")
# If restricted, we still should not have access
if user_role is not None:
assert response.status_code == status.HTTP_403_FORBIDDEN
mock_content.assert_not_called()
# Create an access as a reader. This should unlock the access.
factories.UserDocumentAccessFactory(
document=document, user=user, role=user_role
)
response = client.get(f"/api/v1.0/documents/{document.id!s}/content/")
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["id"] == str(document.id)
assert data["title"] == document.title
assert data["content"] == {"some": "data"}
mock_content.assert_called_once_with(
base64.b64decode(document.content),
"application/vnd.yjs.doc",
"application/json",
)
@pytest.mark.parametrize(
"content_format, accept",
[
("markdown", "text/markdown"),
("html", "text/html"),
("json", "application/json"),
],
)
@patch("core.services.converter_services.YdocConverter.convert")
def test_api_documents_content_format(mock_content, content_format, accept):
"""Test that the content endpoint returns a specific format."""
document = factories.DocumentFactory(link_reach="public")
mock_content.return_value = {"some": "data"}
response = APIClient().get(
f"/api/v1.0/documents/{document.id!s}/content/?content_format={content_format}"
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["id"] == str(document.id)
assert data["title"] == document.title
assert data["content"] == {"some": "data"}
mock_content.assert_called_once_with(
base64.b64decode(document.content), "application/vnd.yjs.doc", accept
)
@patch("core.services.converter_services.YdocConverter._request")
def test_api_documents_content_invalid_format(mock_request):
"""Test that the content endpoint rejects invalid formats."""
document = factories.DocumentFactory(link_reach="public")
response = APIClient().get(
f"/api/v1.0/documents/{document.id!s}/content/?content_format=invalid"
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
mock_request.assert_not_called()
@patch("core.services.converter_services.YdocConverter._request")
def test_api_documents_content_yservice_error(mock_request):
"""Test that service errors are handled properly."""
document = factories.DocumentFactory(link_reach="public")
mock_request.side_effect = requests.RequestException()
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/content/")
mock_request.assert_called_once()
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
@patch("core.services.converter_services.YdocConverter._request")
def test_api_documents_content_nonexistent_document(mock_request):
"""Test that accessing a nonexistent document returns 404."""
client = APIClient()
response = client.get(
"/api/v1.0/documents/00000000-0000-0000-0000-000000000000/content/"
)
assert response.status_code == status.HTTP_404_NOT_FOUND
mock_request.assert_not_called()
@patch("core.services.converter_services.YdocConverter._request")
def test_api_documents_content_empty_document(mock_request):
"""Test that accessing an empty document returns empty content."""
document = factories.DocumentFactory(link_reach="public", content="")
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/content/")
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["id"] == str(document.id)
assert data["title"] == document.title
assert data["content"] is None
mock_request.assert_not_called()
@@ -427,20 +427,3 @@ def test_api_documents_list_favorites_no_extra_queries(django_assert_num_queries
assert result["is_favorite"] is True
else:
assert result["is_favorite"] is False
def test_api_documents_list_throttling(settings):
"""Test api documents throttling."""
current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["document"]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["document"] = "2/minute"
client = APIClient()
for _i in range(2):
response = client.get("/api/v1.0/documents/")
assert response.status_code == 200
with mock.patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get("/api/v1.0/documents/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope document", "warning"
)
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["document"] = current_rate
@@ -37,7 +37,6 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"children_list": True,
"collaboration_auth": True,
"cors_proxy": True,
"content": True,
"descendants": True,
"destroy": False,
"duplicate": False,
@@ -114,7 +113,6 @@ def test_api_documents_retrieve_anonymous_public_parent():
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": False,
# Anonymous user can't favorite a document even with read access
@@ -220,7 +218,6 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": True,
"favorite": True,
@@ -303,7 +300,6 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": True,
"favorite": True,
@@ -498,7 +494,6 @@ def test_api_documents_retrieve_authenticated_related_parent():
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": access.role in ["administrator", "owner"],
"duplicate": True,
"favorite": True,
@@ -81,7 +81,6 @@ def test_api_documents_trashbin_format():
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": True,
"duplicate": True,
"favorite": True,
@@ -3,7 +3,6 @@ Test template accesses API endpoints for users in impress's core app.
"""
import random
from unittest import mock
from uuid import uuid4
import pytest
@@ -774,26 +773,3 @@ def test_api_template_accesses_delete_owners_last_owner(via, mock_user_teams):
assert response.status_code == 403
assert models.TemplateAccess.objects.count() == 2
def test_api_template_accesses_throttling(settings):
"""Test api template accesses throttling."""
current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template_access"]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template_access"] = "2/minute"
template = factories.TemplateFactory()
user = factories.UserFactory()
factories.UserTemplateAccessFactory(
template=template, user=user, role="administrator"
)
client = APIClient()
client.force_login(user)
for _i in range(2):
response = client.get(f"/api/v1.0/templates/{template.id!s}/accesses/")
assert response.status_code == 200
with mock.patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get(f"/api/v1.0/templates/{template.id!s}/accesses/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope template_access", "warning"
)
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template_access"] = current_rate
@@ -218,20 +218,3 @@ def test_api_templates_list_order_param():
assert response_template_ids == templates_ids, (
"created_at values are not sorted from oldest to newest"
)
def test_api_template_throttling(settings):
"""Test api template throttling."""
current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template"]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template"] = "2/minute"
client = APIClient()
for _i in range(2):
response = client.get("/api/v1.0/templates/")
assert response.status_code == 200
with mock.patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get("/api/v1.0/templates/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope template", "warning"
)
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["template"] = current_rate
-18
View File
@@ -3,7 +3,6 @@ Test config API endpoints in the Impress core app.
"""
import json
from unittest.mock import patch
from django.test import override_settings
@@ -175,20 +174,3 @@ def test_api_config_with_original_theme_customization(is_authenticated, settings
theme_customization = json.load(f)
assert content["theme_customization"] == theme_customization
def test_api_config_throttling(settings):
"""Test api config throttling."""
current_rate = settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["config"]
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["config"] = "2/minute"
client = APIClient()
for _i in range(2):
response = client.get("/api/v1.0/config/")
assert response.status_code == 200
with patch("core.api.throttling.capture_message") as mock_capture_message:
response = client.get("/api/v1.0/config/")
assert response.status_code == 429
mock_capture_message.assert_called_once_with(
"Rate limit exceeded for scope config", "warning"
)
settings.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"]["config"] = current_rate
+4 -27
View File
@@ -194,41 +194,18 @@ def test_api_users_list_query_short_queries():
factories.UserFactory(email="john.lennon@example.com")
response = client.get("/api/v1.0/users/?q=jo")
assert response.status_code == 400
assert response.json() == {
"q": ["Ensure this value has at least 5 characters (it has 2)."]
}
assert response.status_code == 200
assert response.json() == []
response = client.get("/api/v1.0/users/?q=john")
assert response.status_code == 400
assert response.json() == {
"q": ["Ensure this value has at least 5 characters (it has 4)."]
}
assert response.status_code == 200
assert response.json() == []
response = client.get("/api/v1.0/users/?q=john.")
assert response.status_code == 200
assert len(response.json()) == 2
def test_api_users_list_query_long_queries():
"""
Queries longer than 255 characters should return an empty result set.
"""
user = factories.UserFactory(email="paul@example.com")
client = APIClient()
client.force_login(user)
factories.UserFactory(email="john.doe@example.com")
factories.UserFactory(email="john.lennon@example.com")
query = "a" * 244
response = client.get(f"/api/v1.0/users/?q={query}@example.com")
assert response.status_code == 400
assert response.json() == {
"q": ["Ensure this value has at most 254 characters (it has 256)."]
}
def test_api_users_list_query_inactive():
"""Inactive users should not be listed."""
user = factories.UserFactory()
@@ -161,7 +161,6 @@ def test_models_documents_get_abilities_forbidden(
"collaboration_auth": False,
"descendants": False,
"cors_proxy": False,
"content": False,
"destroy": False,
"duplicate": False,
"favorite": False,
@@ -225,7 +224,6 @@ def test_models_documents_get_abilities_reader(
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": is_authenticated,
"favorite": is_authenticated,
@@ -291,7 +289,6 @@ def test_models_documents_get_abilities_editor(
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": is_authenticated,
"favorite": is_authenticated,
@@ -346,7 +343,6 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": True,
"duplicate": True,
"favorite": True,
@@ -398,7 +394,6 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": True,
"favorite": True,
@@ -453,7 +448,6 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": True,
"favorite": True,
@@ -515,7 +509,6 @@ def test_models_documents_get_abilities_reader_user(
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": True,
"favorite": True,
@@ -575,7 +568,6 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
"collaboration_auth": True,
"descendants": True,
"cors_proxy": True,
"content": True,
"destroy": False,
"duplicate": True,
"favorite": True,
@@ -1,4 +1,4 @@
"""Test y-provider services."""
"""Test converter services."""
from base64 import b64decode
from unittest.mock import MagicMock, patch
@@ -84,42 +84,6 @@ def test_convert_full_integration(mock_post, settings):
headers={
"Authorization": "Bearer test-key",
"Content-Type": "text/markdown",
"Accept": "application/vnd.yjs.doc",
},
timeout=5,
verify=False,
)
@patch("requests.post")
def test_convert_full_integration_with_specific_headers(mock_post, settings):
"""Test successful conversion with specific content type and accept headers."""
settings.Y_PROVIDER_API_BASE_URL = "http://test.com/"
settings.Y_PROVIDER_API_KEY = "test-key"
settings.CONVERSION_API_ENDPOINT = "conversion-endpoint"
settings.CONVERSION_API_TIMEOUT = 5
settings.CONVERSION_API_SECURE = False
converter = YdocConverter()
expected_response = "# Test Document\n\nThis is test content."
mock_response = MagicMock()
mock_response.text = expected_response
mock_response.raise_for_status.return_value = None
mock_post.return_value = mock_response
result = converter.convert(
b"test_content", "application/vnd.yjs.doc", "text/markdown"
)
assert result == expected_response
mock_post.assert_called_once_with(
"http://test.com/conversion-endpoint/",
data=b"test_content",
headers={
"Authorization": "Bearer test-key",
"Content-Type": "application/vnd.yjs.doc",
"Accept": "text/markdown",
},
timeout=5,
verify=False,
+3 -11
View File
@@ -8,19 +8,11 @@ NB_OBJECTS = {
DEV_USERS = [
{"username": "impress", "email": "impress@impress.world", "language": "en-us"},
{
"username": "user-e2e-webkit",
"email": "user.test@webkit.test",
"language": "en-us",
},
{
"username": "user-e2e-firefox",
"email": "user.test@firefox.test",
"language": "en-us",
},
{"username": "user-e2e-webkit", "email": "user@webkit.test", "language": "en-us"},
{"username": "user-e2e-firefox", "email": "user@firefox.test", "language": "en-us"},
{
"username": "user-e2e-chromium",
"email": "user.test@chromium.test",
"email": "user@chromium.test",
"language": "en-us",
},
]
@@ -119,8 +119,8 @@ def create_demo(stdout):
first_name = random.choice(first_names)
queue.push(
models.User(
admin_email=f"user.test{i:d}@example.com",
email=f"user.test{i:d}@example.com",
admin_email=f"user{i:d}@example.com",
email=f"user{i:d}@example.com",
password="!",
is_superuser=False,
is_active=True,
@@ -33,9 +33,9 @@ def test_commands_create_demo():
# assert dev users have doc accesses
user = models.User.objects.get(email="impress@impress.world")
assert models.DocumentAccess.objects.filter(user=user).exists()
user = models.User.objects.get(email="user.test@webkit.test")
user = models.User.objects.get(email="user@webkit.test")
assert models.DocumentAccess.objects.filter(user=user).exists()
user = models.User.objects.get(email="user.test@firefox.test")
user = models.User.objects.get(email="user@firefox.test")
assert models.DocumentAccess.objects.filter(user=user).exists()
user = models.User.objects.get(email="user.test@chromium.test")
user = models.User.objects.get(email="user@chromium.test")
assert models.DocumentAccess.objects.filter(user=user).exists()
-41
View File
@@ -356,9 +356,6 @@ class Base(Configuration):
"PAGE_SIZE": 20,
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_THROTTLE_CLASSES": [
"lasuite.drf.throttling.MonitoredScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {
"user_list_sustained": values.Value(
default="180/hour",
@@ -370,46 +367,8 @@ class Base(Configuration):
environ_name="API_USERS_LIST_THROTTLE_RATE_BURST",
environ_prefix=None,
),
"document": values.Value(
default="80/minute",
environ_name="API_DOCUMENT_THROTTLE_RATE",
environ_prefix=None,
),
"document_access": values.Value(
default="50/minute",
environ_name="API_DOCUMENT_ACCESS_THROTTLE_RATE",
environ_prefix=None,
),
"template": values.Value(
default="30/minute",
environ_name="API_TEMPLATE_THROTTLE_RATE",
environ_prefix=None,
),
"template_access": values.Value(
default="30/minute",
environ_name="API_TEMPLATE_ACCESS_THROTTLE_RATE",
environ_prefix=None,
),
"invitation": values.Value(
default="60/minute",
environ_name="API_INVITATION_THROTTLE_RATE",
environ_prefix=None,
),
"document_ask_for_access": values.Value(
default="30/minute",
environ_name="API_DOCUMENT_ASK_FOR_ACCESS_THROTTLE_RATE",
environ_prefix=None,
),
"config": values.Value(
default="30/minute",
environ_name="API_CONFIG_THROTTLE_RATE",
environ_prefix=None,
),
},
}
MONITORED_THROTTLE_FAILURE_CALLBACK = (
"core.api.throttling.sentry_monitoring_throttle_failure"
)
SPECTACULAR_SETTINGS = {
"TITLE": "Impress API",
+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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Breton\n"
"Language: br_FR\n"
@@ -70,7 +70,7 @@ msgstr "Doare korf"
msgid "Format"
msgstr "Stumm"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "eilenn {title}"
@@ -225,8 +225,8 @@ msgstr "implijer"
msgid "users"
msgstr "implijerien"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "titl"
@@ -242,155 +242,155 @@ msgstr "Restr"
msgid "Documents"
msgstr "Restroù"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Restr hep titl"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} en deus rannet ur restr ganeoc'h!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} en deus pedet ac'hanoc'h gant ar rol \"{role}\" war ar restr da-heul:"
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} en deus rannet ur restr ganeoc'h: {title}"
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Roud liamm ar restr/an implijer"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Roudoù liamm ar restr/an implijer"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr "Ur roud liamm a zo dija evit an restr/an implijer."
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Restr muiañ-karet"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Restroù muiañ-karet"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Ar restr-mañ a zo ur restr muiañ karet gant an implijer-mañ."
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Liamm restr/implijer"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Liammoù restr/implijer"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "An implijer-mañ a zo dija er restr-mañ."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Ar skipailh-mañ a zo dija en restr-mañ."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr "An implijer pe ar skipailh a rank bezañ termenet, ket an daou avat."
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr "Goulenn tizhout ar restr"
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr "Goulennoù tizhout ar restr"
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr "An implijer en deus goulennet tizhout ar restr-mañ."
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr "{name} en defe c'hoant da dizhout ar restr-mañ!"
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr "{name} en defe c'hoant da dizhout ar restr da-heul:"
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr "{name} en defe c'hoant da dizhout ar restr: {title}"
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "deskrivadur"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "kod"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "publik"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "M'eo foran ar patrom-mañ hag implijus gant n'eus forzh piv."
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Patrom"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Patromoù"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Liamm patrom/implijer"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Liammoù patrom/implijer"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "An implijer-mañ a zo dija er patrom-mañ."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Ar skipailh-mañ a zo dija er patrom-mañ."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "postel"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Pedadenn d'ur restr"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Pedadennoù d'ur restr"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
msgid "This email is already associated to a registered user."
msgstr "Ar postel-mañ a zo liammet ouzh un implijer enskrivet."
+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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -70,7 +70,7 @@ msgstr "Typ"
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "Kopie von {title}"
@@ -225,8 +225,8 @@ msgstr "Benutzer"
msgid "users"
msgstr "Benutzer"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "Titel"
@@ -242,155 +242,155 @@ msgstr "Dokument"
msgid "Documents"
msgstr "Dokumente"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Unbenanntes Dokument"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, 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:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, 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:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Dokument/Benutzer Linkverfolgung"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
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:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Dokumentenfavorit"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Dokumentfavoriten"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
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:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Dokument/Benutzerbeziehung"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Dokument/Benutzerbeziehungen"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
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:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "Beschreibung"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "Code"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "öffentlich"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "Ob diese Vorlage für jedermann öffentlich ist."
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Vorlage"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Vorlagen"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Vorlage/Benutzer-Beziehung"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Vorlage/Benutzerbeziehungen"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Dieser Benutzer ist bereits in dieser Vorlage."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Dieses Team ist bereits in diesem Template."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "E-Mail-Adresse"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Einladung zum Dokument"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Dokumenteinladungen"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: English\n"
"Language: en_US\n"
@@ -70,7 +70,7 @@ msgstr ""
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr ""
@@ -242,155 +242,155 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr ""
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr ""
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr ""
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr ""
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
msgid "This email is already associated to a registered user."
msgstr ""
+48 -48
View File
@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: lasuite-docs\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -70,7 +70,7 @@ msgstr "Tipo de Cuerpo"
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "copia de {title}"
@@ -163,7 +163,7 @@ msgstr "sub (UUID)"
#: build/lib/core/models.py:142 core/models.py:142
msgid "Required. 255 characters or fewer. ASCII characters only."
msgstr "Obligatorio. 255 caracteres o menos. Solo caracteres ASCII."
msgstr ""
#: build/lib/core/models.py:150 core/models.py:150
msgid "full name"
@@ -225,8 +225,8 @@ msgstr "usuario"
msgid "users"
msgstr "usuarios"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "título"
@@ -242,155 +242,155 @@ msgstr "Documento"
msgid "Documents"
msgstr "Documentos"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Documento sin título"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "¡{name} ha compartido un documento contigo!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, 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:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} ha compartido un documento contigo: {title}"
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Traza del enlace de documento/usuario"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Trazas del enlace de documento/usuario"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
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:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Documento favorito"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Documentos favoritos"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
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:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Relación documento/usuario"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Relaciones documento/usuario"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Este usuario ya forma parte del documento."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Este equipo ya forma parte del documento."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
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:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr "Solicitud de acceso"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr "Solicitud de accesos"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr "Este usuario ya ha solicitado acceso a este documento."
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr "¡{name} desea acceder a un documento!"
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr "{name} desea acceso al siguiente documento:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr "{name} está pidiendo acceso al documento: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "descripción"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "código"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "público"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
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:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Plantilla"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Plantillas"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Relación plantilla/usuario"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Relaciones plantilla/usuario"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Este usuario ya forma parte de la plantilla."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Este equipo ya se encuentra en esta plantilla."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "dirección de correo electrónico"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Invitación al documento"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Invitaciones a documentos"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 11:45\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -70,7 +70,7 @@ msgstr "Type de corps"
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "copie de {title}"
@@ -225,8 +225,8 @@ msgstr "utilisateur"
msgid "users"
msgstr "utilisateurs"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "titre"
@@ -242,155 +242,155 @@ msgstr "Document"
msgid "Documents"
msgstr "Documents"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Document sans titre"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} a partagé un document avec vous!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, 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:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, 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:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Trace du lien document/utilisateur"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Traces du lien document/utilisateur"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
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:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Document favori"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Documents favoris"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
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:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Relation document/utilisateur"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Relations document/utilisateur"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Cet utilisateur est déjà dans ce document."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Cette équipe est déjà dans ce document."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
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:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr "Demande d'accès au document"
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr "Demande d'accès au document"
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr "Cet utilisateur a déjà demandé l'accès à ce document."
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr "{name} souhaiterait accéder au document suivant !"
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr "{name} souhaiterait accéder au document suivant :"
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr "{name} demande l'accès au document : {title}"
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "description"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "CSS"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "public"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
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:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Modèle"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Modèles"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Relation modèle/utilisateur"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Relations modèle/utilisateur"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Cet utilisateur est déjà dans ce modèle."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Cette équipe est déjà modèle."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "adresse e-mail"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Invitation à un document"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Invitations à un document"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -70,7 +70,7 @@ msgstr ""
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "copia di {title}"
@@ -225,8 +225,8 @@ msgstr "utente"
msgid "users"
msgstr "utenti"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "titolo"
@@ -242,155 +242,155 @@ msgstr "Documento"
msgid "Documents"
msgstr "Documenti"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Documento senza titolo"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} ha condiviso un documento con te!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, 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:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, 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:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Documento preferito"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Documenti preferiti"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Questo utente è già presente in questo documento."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Questo team è già presente in questo documento."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "descrizione"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "pubblico"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "Indica se questo modello è pubblico per chiunque."
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Modello"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Modelli"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Questo utente è già in questo modello."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Questo team è già in questo modello."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "indirizzo e-mail"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Invito al documento"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Inviti al documento"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -70,7 +70,7 @@ msgstr "Text type"
msgid "Format"
msgstr "Formaat"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "kopie van {title}"
@@ -225,8 +225,8 @@ msgstr "gebruiker"
msgid "users"
msgstr "gebruikers"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "titel"
@@ -242,155 +242,155 @@ msgstr "Document"
msgid "Documents"
msgstr "Documenten"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Naamloos Document"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} heeft een document met gedeeld!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, 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:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, 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:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Document/gebruiker url"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Document/gebruiker url"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
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:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Document favoriet"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Document favorieten"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
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:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Document/gebruiker relatie"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Document/gebruiker relaties"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "De gebruiker is al in dit document."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Het team is al in dit document."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
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:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "omschrijving"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "code"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "publiek"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
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:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Template"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Templates"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Template/gebruiker relatie"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Template/gebruiker relaties"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "De gebruiker bestaat al in dit template."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Het team bestaat al in dit template."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "email adres"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Document uitnodiging"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Document uitnodigingen"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -70,7 +70,7 @@ msgstr "Tipo de corpo"
msgid "Format"
msgstr "Formato"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "cópia de {title}"
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr ""
@@ -242,155 +242,155 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr ""
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr ""
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr ""
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr ""
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -70,7 +70,7 @@ msgstr "Тип сообщения"
msgid "Format"
msgstr "Формат"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "копия {title}"
@@ -225,8 +225,8 @@ msgstr "пользователь"
msgid "users"
msgstr "пользователи"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "заголовок"
@@ -242,155 +242,155 @@ msgstr "Документ"
msgid "Documents"
msgstr "Документы"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Безымянный документ"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} делится с вами документом!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} приглашает вас присоединиться к следующему документу с ролью \"{role}\":"
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} делится с вами документом: {title}"
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Трассировка связи документ/пользователь"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Трассировка связей документ/пользователь"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr "Для этого документа/пользователя уже существует трассировка ссылки."
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Избранный документ"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Избранные документы"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Этот документ уже помечен как избранный для этого пользователя."
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Отношение документ/пользователь"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Отношения документ/пользователь"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Этот пользователь уже имеет доступ к этому документу."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Эта команда уже имеет доступ к этому документу."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr "Может быть выбран либо пользователь, либо команда, но не оба варианта сразу."
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr "Документ запрашивает доступ"
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr "Документ запрашивает доступы"
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr "Этот пользователь уже запросил доступ к этому документу."
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr "{name} хочет получить доступ к документу!"
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr "{name} хочет получить доступ к следующему документу:"
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr "{name} запрашивает доступ к документу: {title}"
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "описание"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "код"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "доступно всем"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "Этот шаблон доступен всем пользователям."
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Шаблон"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Шаблоны"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Отношение шаблон/пользователь"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Отношения шаблон/пользователь"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Этот пользователь уже указан в этом шаблоне."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Эта команда уже указана в этом шаблоне."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "адрес электронной почты"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Приглашение для документа"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Приглашения для документов"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Slovenian\n"
"Language: sl_SI\n"
@@ -70,7 +70,7 @@ msgstr "Vrsta telesa"
msgid "Format"
msgstr "Oblika"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr ""
@@ -225,8 +225,8 @@ msgstr "uporabnik"
msgid "users"
msgstr "uporabniki"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "naslov"
@@ -242,155 +242,155 @@ msgstr "Dokument"
msgid "Documents"
msgstr "Dokumenti"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Dokument brez naslova"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} je delil dokument z vami!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, 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:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} je delil dokument z vami: {title}"
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Dokument/sled povezave uporabnika"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Sledi povezav dokumenta/uporabnika"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr "Za ta dokument/uporabnika že obstaja sled povezave."
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Priljubljeni dokument"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Priljubljeni dokumenti"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
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:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Odnos dokument/uporabnik"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Odnosi dokument/uporabnik"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Ta uporabnik je že v tem dokumentu."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Ta ekipa je že v tem dokumentu."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
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:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "opis"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "koda"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "javno"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "Ali je ta predloga javna za uporabo."
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Predloga"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Predloge"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Odnos predloga/uporabnik"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Odnosi med predlogo in uporabnikom"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Ta uporabnik je že v tej predlogi."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Ta ekipa je že v tej predlogi."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "elektronski naslov"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Vabilo na dokument"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Vabila na dokument"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
@@ -70,7 +70,7 @@ msgstr ""
msgid "Format"
msgstr "Format"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr ""
@@ -242,155 +242,155 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr ""
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr ""
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr ""
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr ""
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "e-postadress"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Bjud in dokument"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Inbjudningar dokument"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
@@ -70,7 +70,7 @@ msgstr ""
msgid "Format"
msgstr ""
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr ""
@@ -225,8 +225,8 @@ msgstr ""
msgid "users"
msgstr ""
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr ""
@@ -242,155 +242,155 @@ msgstr ""
msgid "Documents"
msgstr ""
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr ""
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr ""
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr ""
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr ""
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr ""
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr ""
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr ""
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr ""
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr ""
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr ""
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr ""
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr ""
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr ""
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr ""
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr ""
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr ""
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr ""
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr ""
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr ""
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr ""
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr ""
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr ""
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr ""
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr ""
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr ""
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr ""
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr ""
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr ""
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr ""
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
@@ -70,7 +70,7 @@ msgstr "Тип вмісту"
msgid "Format"
msgstr "Формат"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "копія {title}"
@@ -225,8 +225,8 @@ msgstr "користувач"
msgid "users"
msgstr "користувачі"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "заголовок"
@@ -242,155 +242,155 @@ msgstr "Документ"
msgid "Documents"
msgstr "Документи"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "Документ без назви"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} ділиться з вами документом!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} запрошує вас для роботи з документом із роллю \"{role}\":"
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} ділиться з вами документом: {title}"
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "Трасування посилання Документ/користувач"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "Трасування посилань Документ/користувач"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr "Відстеження вже існуючих посилань для цього документа/користувача."
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "Обраний документ"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "Обрані документи"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "Цей документ вже вказаний як обраний для одного користувача."
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "Відносини документ/користувач"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "Відносини документ/користувач"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "Цей користувач вже має доступ до цього документу."
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "Ця команда вже має доступ до цього документа."
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr "Вкажіть користувача або команду, а не обох."
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr "Запит доступу до документа"
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr "Запит доступу для документа"
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr "Цей користувач вже попросив доступ до цього документа."
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr "{name} хоче отримати доступ до документа!"
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr "{name} бажає отримати доступ до наступного документа:"
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr "{name} запитує доступ до документа: {title}"
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "опис"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "код"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "публічне"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "Чи є цей шаблон публічним для будь-кого користувача."
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "Шаблон"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "Шаблони"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "Відношення шаблон/користувач"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "Відношення шаблон/користувач"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "Цей користувач вже має доступ до цього шаблону."
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "Ця команда вже має доступ до цього шаблону."
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "електронна адреса"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "Запрошення до редагування документа"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "Запрошення до редагування документів"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
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-09-10 14:29+0000\n"
"PO-Revision-Date: 2025-09-12 09:50\n"
"POT-Creation-Date: 2025-09-01 21:01+0000\n"
"PO-Revision-Date: 2025-09-04 10:03\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -70,7 +70,7 @@ msgstr "正文类型"
msgid "Format"
msgstr "格式"
#: build/lib/core/api/viewsets.py:965 core/api/viewsets.py:965
#: build/lib/core/api/viewsets.py:960 core/api/viewsets.py:960
#, python-brace-format
msgid "copy of {title}"
msgstr "{title} 的副本"
@@ -225,8 +225,8 @@ msgstr "用户"
msgid "users"
msgstr "个用户"
#: build/lib/core/models.py:359 build/lib/core/models.py:1281
#: core/models.py:359 core/models.py:1281
#: build/lib/core/models.py:359 build/lib/core/models.py:1280
#: core/models.py:359 core/models.py:1280
msgid "title"
msgstr "标题"
@@ -242,155 +242,155 @@ msgstr "文档"
msgid "Documents"
msgstr "个文档"
#: build/lib/core/models.py:422 build/lib/core/models.py:819 core/models.py:422
#: core/models.py:819
#: build/lib/core/models.py:422 build/lib/core/models.py:818 core/models.py:422
#: core/models.py:818
msgid "Untitled Document"
msgstr "未命名文档"
#: build/lib/core/models.py:854 core/models.py:854
#: build/lib/core/models.py:853 core/models.py:853
#, python-brace-format
msgid "{name} shared a document with you!"
msgstr "{name} 与您共享了一个文档!"
#: build/lib/core/models.py:858 core/models.py:858
#: build/lib/core/models.py:857 core/models.py:857
#, python-brace-format
msgid "{name} invited you with the role \"{role}\" on the following document:"
msgstr "{name} 邀请您以“{role}”角色访问以下文档:"
#: build/lib/core/models.py:864 core/models.py:864
#: build/lib/core/models.py:863 core/models.py:863
#, python-brace-format
msgid "{name} shared a document with you: {title}"
msgstr "{name} 与您共享了一个文档:{title}"
#: build/lib/core/models.py:964 core/models.py:964
#: build/lib/core/models.py:963 core/models.py:963
msgid "Document/user link trace"
msgstr "文档/用户链接跟踪"
#: build/lib/core/models.py:965 core/models.py:965
#: build/lib/core/models.py:964 core/models.py:964
msgid "Document/user link traces"
msgstr "个文档/用户链接跟踪"
#: build/lib/core/models.py:971 core/models.py:971
#: build/lib/core/models.py:970 core/models.py:970
msgid "A link trace already exists for this document/user."
msgstr "此文档/用户的链接跟踪已存在。"
#: build/lib/core/models.py:994 core/models.py:994
#: build/lib/core/models.py:993 core/models.py:993
msgid "Document favorite"
msgstr "文档收藏"
#: build/lib/core/models.py:995 core/models.py:995
#: build/lib/core/models.py:994 core/models.py:994
msgid "Document favorites"
msgstr "文档收藏夹"
#: build/lib/core/models.py:1001 core/models.py:1001
#: build/lib/core/models.py:1000 core/models.py:1000
msgid "This document is already targeted by a favorite relation instance for the same user."
msgstr "该文档已被同一用户的收藏关系实例关联。"
#: build/lib/core/models.py:1023 core/models.py:1023
#: build/lib/core/models.py:1022 core/models.py:1022
msgid "Document/user relation"
msgstr "文档/用户关系"
#: build/lib/core/models.py:1024 core/models.py:1024
#: build/lib/core/models.py:1023 core/models.py:1023
msgid "Document/user relations"
msgstr "文档/用户关系集"
#: build/lib/core/models.py:1030 core/models.py:1030
#: build/lib/core/models.py:1029 core/models.py:1029
msgid "This user is already in this document."
msgstr "该用户已在此文档中。"
#: build/lib/core/models.py:1036 core/models.py:1036
#: build/lib/core/models.py:1035 core/models.py:1035
msgid "This team is already in this document."
msgstr "该团队已在此文档中。"
#: build/lib/core/models.py:1042 build/lib/core/models.py:1367
#: core/models.py:1042 core/models.py:1367
#: build/lib/core/models.py:1041 build/lib/core/models.py:1366
#: core/models.py:1041 core/models.py:1366
msgid "Either user or team must be set, not both."
msgstr "必须设置用户或团队之一,不能同时设置两者。"
#: build/lib/core/models.py:1188 core/models.py:1188
#: build/lib/core/models.py:1187 core/models.py:1187
msgid "Document ask for access"
msgstr ""
#: build/lib/core/models.py:1189 core/models.py:1189
#: build/lib/core/models.py:1188 core/models.py:1188
msgid "Document ask for accesses"
msgstr ""
#: build/lib/core/models.py:1195 core/models.py:1195
#: build/lib/core/models.py:1194 core/models.py:1194
msgid "This user has already asked for access to this document."
msgstr ""
#: build/lib/core/models.py:1260 core/models.py:1260
#: build/lib/core/models.py:1259 core/models.py:1259
#, python-brace-format
msgid "{name} would like access to a document!"
msgstr ""
#: build/lib/core/models.py:1264 core/models.py:1264
#: build/lib/core/models.py:1263 core/models.py:1263
#, python-brace-format
msgid "{name} would like access to the following document:"
msgstr ""
#: build/lib/core/models.py:1270 core/models.py:1270
#: build/lib/core/models.py:1269 core/models.py:1269
#, python-brace-format
msgid "{name} is asking for access to the document: {title}"
msgstr ""
#: build/lib/core/models.py:1282 core/models.py:1282
#: build/lib/core/models.py:1281 core/models.py:1281
msgid "description"
msgstr "说明"
#: build/lib/core/models.py:1283 core/models.py:1283
#: build/lib/core/models.py:1282 core/models.py:1282
msgid "code"
msgstr "代码"
#: build/lib/core/models.py:1284 core/models.py:1284
#: build/lib/core/models.py:1283 core/models.py:1283
msgid "css"
msgstr "css"
#: build/lib/core/models.py:1286 core/models.py:1286
#: build/lib/core/models.py:1285 core/models.py:1285
msgid "public"
msgstr "公开"
#: build/lib/core/models.py:1288 core/models.py:1288
#: build/lib/core/models.py:1287 core/models.py:1287
msgid "Whether this template is public for anyone to use."
msgstr "该模板是否公开供任何人使用。"
#: build/lib/core/models.py:1294 core/models.py:1294
#: build/lib/core/models.py:1293 core/models.py:1293
msgid "Template"
msgstr "模板"
#: build/lib/core/models.py:1295 core/models.py:1295
#: build/lib/core/models.py:1294 core/models.py:1294
msgid "Templates"
msgstr "模板"
#: build/lib/core/models.py:1348 core/models.py:1348
#: build/lib/core/models.py:1347 core/models.py:1347
msgid "Template/user relation"
msgstr "模板/用户关系"
#: build/lib/core/models.py:1349 core/models.py:1349
#: build/lib/core/models.py:1348 core/models.py:1348
msgid "Template/user relations"
msgstr "模板/用户关系集"
#: build/lib/core/models.py:1355 core/models.py:1355
#: build/lib/core/models.py:1354 core/models.py:1354
msgid "This user is already in this template."
msgstr "该用户已在此模板中。"
#: build/lib/core/models.py:1361 core/models.py:1361
#: build/lib/core/models.py:1360 core/models.py:1360
msgid "This team is already in this template."
msgstr "该团队已在此模板中。"
#: build/lib/core/models.py:1438 core/models.py:1438
#: build/lib/core/models.py:1437 core/models.py:1437
msgid "email address"
msgstr "电子邮件地址"
#: build/lib/core/models.py:1457 core/models.py:1457
#: build/lib/core/models.py:1456 core/models.py:1456
msgid "Document invitation"
msgstr "文档邀请"
#: build/lib/core/models.py:1458 core/models.py:1458
#: build/lib/core/models.py:1457 core/models.py:1457
msgid "Document invitations"
msgstr "文档邀请"
#: build/lib/core/models.py:1478 core/models.py:1478
#: build/lib/core/models.py:1477 core/models.py:1477
msgid "This email is already associated to a registered user."
msgstr "此电子邮件已经与现有注册用户关联。"
+3 -3
View File
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "impress"
version = "3.7.0"
version = "3.6.0"
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
classifiers = [
"Development Status :: 5 - Production/Stable",
@@ -34,12 +34,12 @@ dependencies = [
"django-countries==7.6.1",
"django-csp==4.0",
"django-filter==25.1",
"django-lasuite[all]==0.0.14",
"django-lasuite[all]==0.0.11",
"django-parler==2.3",
"django-redis==6.0.0",
"django-storages[s3]==1.14.6",
"django-timezone-field>=5.1",
"django==5.2.6",
"django==5.2.4",
"django-treebeard==4.7.1",
"djangorestframework==3.16.0",
"drf_spectacular==0.28.0",
+2 -2
View File
@@ -10,13 +10,13 @@ WORKDIR /home/frontend/
COPY ./src/frontend/package.json ./package.json
COPY ./src/frontend/yarn.lock ./yarn.lock
COPY ./src/frontend/apps/impress/package.json ./apps/impress/package.json
COPY ./src/frontend/packages/eslint-plugin-docs/package.json ./packages/eslint-plugin-docs/package.json
COPY ./src/frontend/packages/eslint-config-impress/package.json ./packages/eslint-config-impress/package.json
RUN yarn install --frozen-lockfile
COPY .dockerignore ./.dockerignore
COPY ./src/frontend/.prettierrc.js ./.prettierrc.js
COPY ./src/frontend/packages/eslint-plugin-docs ./packages/eslint-plugin-docs
COPY ./src/frontend/packages/eslint-config-impress ./packages/eslint-config-impress
COPY ./src/frontend/apps/impress ./apps/impress
### ---- Front-end builder image ----
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
root: true,
extends: ['impress/playwright'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
ignorePatterns: ['node_modules'],
};
@@ -5,19 +5,14 @@ import { keyCloakSignIn } from './utils-common';
const saveStorageState = async (
browserConfig: FullProject<unknown, unknown>,
) => {
if (!browserConfig) {
throw new Error('No browser config found');
}
const browserName = browserConfig?.name || 'chromium';
const browserName = browserConfig.name || 'chromium';
const { storageState, ...useConfig } = browserConfig.use;
const { storageState, ...useConfig } = browserConfig?.use;
const browser = await chromium.launch();
const context = await browser.newContext(useConfig);
const page = await context.newPage();
try {
// eslint-disable-next-line playwright/no-networkidle
await page.goto('/', { waitUntil: 'networkidle' });
await page.content();
await expect(page.getByText('Docs').first()).toBeVisible();
@@ -50,9 +45,11 @@ const saveStorageState = async (
};
async function globalSetup(config: FullConfig) {
/* eslint-disable @typescript-eslint/no-non-null-assertion */
const chromeConfig = config.projects.find((p) => p.name === 'chromium')!;
const firefoxConfig = config.projects.find((p) => p.name === 'firefox')!;
const webkitConfig = config.projects.find((p) => p.name === 'webkit')!;
/* eslint-enable @typescript-eslint/no-non-null-assertion */
await saveStorageState(chromeConfig);
await saveStorageState(webkitConfig);
@@ -50,7 +50,7 @@ test.describe('Config', () => {
await expect(image).toBeVisible();
// Wait for the media-check to be processed
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
// Check src of image
@@ -45,8 +45,8 @@ test.describe('Doc Create', () => {
})
.click();
const input = page.getByRole('textbox', { name: 'Document title' });
await expect(input).toHaveText('', { timeout: 10000 });
const input = page.getByRole('textbox', { name: 'doc title input' });
await expect(input).toHaveText('');
await expect(
page.locator('.c__tree-view--row-content').getByText('Untitled document'),
).toBeVisible();
@@ -67,8 +67,8 @@ test.describe('Doc Create', () => {
.getByText('New sub-doc')
.click();
const input = page.getByRole('textbox', { name: 'Document title' });
await expect(input).toHaveText('', { timeout: 10000 });
const input = page.getByRole('textbox', { name: 'doc title input' });
await expect(input).toHaveText('');
await expect(
page.locator('.c__tree-view--row-content').getByText('Untitled document'),
).toBeVisible();
@@ -1,4 +1,3 @@
/* eslint-disable playwright/no-conditional-expect */
import path from 'path';
import { chromium, expect, test } from '@playwright/test';
@@ -215,6 +214,7 @@ test.describe('Doc Editor', () => {
});
test('it saves the doc when we quit pages', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(browserName === 'webkit', 'This test is very flaky with webkit');
// Check the first doc
@@ -226,13 +226,9 @@ test.describe('Doc Editor', () => {
await editor.fill('Hello World Doc persisted 2');
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
await page.waitForTimeout(1000);
const urlDoc = page.url();
await page.goto(urlDoc);
// Wait for editor to load
await expect(editor).toBeVisible();
await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible();
});
@@ -283,7 +279,7 @@ test.describe('Doc Editor', () => {
await expect(image).toBeVisible();
// Wait for the media-check to be processed
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
// Check src of image
@@ -401,6 +397,8 @@ test.describe('Doc Editor', () => {
const editor = page.locator('.ProseMirror');
await editor.getByText('Hello').selectText();
/* eslint-disable playwright/no-conditional-expect */
/* eslint-disable playwright/no-conditional-in-test */
if (!ai_transform && !ai_translate) {
await expect(page.getByRole('button', { name: 'AI' })).toBeHidden();
return;
@@ -427,6 +425,8 @@ test.describe('Doc Editor', () => {
page.getByRole('menuitem', { name: 'Language' }),
).toBeHidden();
}
/* eslint-enable playwright/no-conditional-expect */
/* eslint-enable playwright/no-conditional-in-test */
});
});
@@ -467,14 +467,12 @@ test.describe('Doc Editor', () => {
await expect(
page.getByRole('button', {
name: 'Download',
exact: true,
}),
).toBeVisible();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
@@ -703,20 +701,8 @@ test.describe('Doc Editor', () => {
const emojiButton = calloutBlock.getByRole('button');
await expect(emojiButton).toHaveText('💡');
await emojiButton.click();
// Group smiley
await expect(page.getByRole('button', { name: '🤠' })).toBeVisible();
// Group animals
await page.getByText('Animals & Nature').scrollIntoViewIfNeeded();
await expect(page.getByRole('button', { name: '🦆' })).toBeVisible();
// Group travel
await page.getByText('Travel & Places').scrollIntoViewIfNeeded();
await expect(page.getByRole('button', { name: '🚝' })).toBeVisible();
// Group objects
await page.getByText('Objects').scrollIntoViewIfNeeded();
await expect(page.getByRole('button', { name: '🪇' })).toBeVisible();
// Group symbol
await page.getByText('Symbols').scrollIntoViewIfNeeded();
await expect(page.getByRole('button', { name: '🛃' })).toBeVisible();
await page.locator('button[aria-label="⚠️"]').click();
await expect(emojiButton).toHaveText('⚠️');
await page.locator('.bn-side-menu > button').last().click();
await page.locator('.mantine-Menu-dropdown > button').last().click();
@@ -29,7 +29,12 @@ test.describe('Doc Export', () => {
})
.click();
await expect(page.getByTestId('modal-export-title')).toBeVisible();
await expect(
page
.locator('div')
.filter({ hasText: /^Download$/ })
.first(),
).toBeVisible();
await expect(
page.getByText('Download your document in a .docx or .pdf format.'),
).toBeVisible();
@@ -38,11 +43,9 @@ test.describe('Doc Export', () => {
).toBeVisible();
await expect(page.getByRole('combobox', { name: 'Format' })).toBeVisible();
await expect(
page.getByRole('button', {
name: 'Close the download modal',
}),
page.getByRole('button', { name: 'Close the modal' }),
).toBeVisible();
await expect(page.getByTestId('doc-export-download-button')).toBeVisible();
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
});
test('it exports the doc with pdf line break', async ({
@@ -83,7 +86,12 @@ test.describe('Doc Export', () => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -128,13 +136,23 @@ test.describe('Doc Export', () => {
await page.getByRole('combobox', { name: 'Format' }).click();
await page.getByRole('option', { name: 'Docx' }).click();
await expect(page.getByTestId('doc-export-download-button')).toBeVisible();
await expect(
page.getByRole('button', {
name: 'Download',
exact: true,
}),
).toBeVisible();
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.docx`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.docx`);
@@ -200,7 +218,11 @@ test.describe('Doc Export', () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
await expect(page.getByTestId('doc-export-download-button')).toBeVisible();
await expect(
page.getByRole('button', {
name: 'Download',
}),
).toBeVisible();
const responseCorsPromise = page.waitForResponse(
(response) =>
@@ -211,7 +233,11 @@ test.describe('Doc Export', () => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
})
.click();
const responseCors = await responseCorsPromise;
expect(responseCors.ok()).toBe(true);
@@ -253,13 +279,21 @@ test.describe('Doc Export', () => {
})
.click();
await expect(page.getByTestId('doc-export-download-button')).toBeVisible();
await expect(
page.getByRole('button', {
name: 'Download',
}),
).toBeVisible();
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -296,14 +330,22 @@ test.describe('Doc Export', () => {
.click();
await expect(
page.getByTestId('doc-open-modal-download-button'),
page.getByRole('button', {
name: 'Download',
exact: true,
}),
).toBeVisible();
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -350,14 +392,22 @@ test.describe('Doc Export', () => {
.click();
await expect(
page.getByTestId('doc-open-modal-download-button'),
page.getByRole('button', {
name: 'Download',
exact: true,
}),
).toBeVisible();
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${randomDoc}.pdf`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
@@ -393,9 +443,14 @@ test.describe('Doc Export', () => {
})
.click();
const input = page.locator('.--docs--doc-title-input[role="textbox"]');
await page.waitForURL('**/docs/**', {
timeout: 10000,
waitUntil: 'domcontentloaded',
});
const input = page.getByLabel('doc title input');
await expect(input).toBeVisible();
await expect(input).toHaveText('', { timeout: 10000 });
await expect(input).toHaveText('');
await input.click();
await input.fill(randomDocFrench);
await input.blur();
@@ -406,7 +461,7 @@ test.describe('Doc Export', () => {
await page
.getByRole('button', {
name: 'Exporter le document',
name: 'Export the document',
})
.click();
@@ -414,7 +469,12 @@ test.describe('Doc Export', () => {
return download.suggestedFilename().includes(`${randomDocFrench}.pdf`);
});
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Télécharger',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDocFrench}.pdf`);
@@ -449,23 +509,19 @@ test.describe('Doc Export', () => {
await page.locator('.bn-block-outer').last().fill('/');
await page.getByText('Link a doc').first().click();
const input = page.locator(
"span[data-inline-content-type='interlinkingSearchInline'] input",
);
const searchContainer = page.locator('.quick-search-container');
await page
.locator(
"span[data-inline-content-type='interlinkingSearchInline'] input",
)
.fill('interlink-child');
await input.fill('export-interlink');
await page
.locator('.quick-search-container')
.getByText('interlink-child')
.click();
await expect(searchContainer).toBeVisible();
await expect(searchContainer.getByText(randomDoc)).toBeVisible();
// We are in docChild, we want to create a link to randomDoc (parent)
await searchContainer.getByText(randomDoc).click();
// Search the interlinking link in the editor (not in the document tree)
const editor = page.locator('.ProseMirror.bn-editor');
const interlink = editor.getByRole('link', {
name: randomDoc,
const interlink = page.getByRole('link', {
name: 'interlink-child',
});
await expect(interlink).toBeVisible();
@@ -480,7 +536,12 @@ test.describe('Doc Export', () => {
})
.click();
void page.getByTestId('doc-export-download-button').click();
void page
.getByRole('button', {
name: 'Download',
exact: true,
})
.click();
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${docChild}.pdf`);
@@ -488,6 +549,6 @@ test.describe('Doc Export', () => {
const pdfBuffer = await cs.toBuffer(await download.createReadStream());
const pdfData = await pdf(pdfBuffer);
expect(pdfData.text).toContain(randomDoc);
expect(pdfData.text).toContain('interlink-child'); // This is the pdf text
});
});
@@ -36,8 +36,9 @@ test.describe('Doc grid dnd', () => {
expect(draggableBoundingBox).toBeDefined();
expect(dropZoneBoundingBox).toBeDefined();
// eslint-disable-next-line playwright/no-conditional-in-test
if (!draggableBoundingBox || !dropZoneBoundingBox) {
throw new Error('Unable to determine the position of the elements');
throw new Error('Impossible de déterminer la position des éléments');
}
await page.mouse.move(
@@ -85,8 +86,9 @@ test.describe('Doc grid dnd', () => {
const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox();
// eslint-disable-next-line playwright/no-conditional-in-test
if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) {
throw new Error('Unable to determine the position of the elements');
throw new Error('Impossible de déterminer la position des éléments');
}
await page.mouse.move(
@@ -135,8 +137,9 @@ test.describe('Doc grid dnd', () => {
const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox();
// eslint-disable-next-line playwright/no-conditional-in-test
if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) {
throw new Error('Unable to determine the position of the elements');
throw new Error('Impossible de déterminer la position des éléments');
}
await page.mouse.move(
@@ -80,7 +80,9 @@ test.describe('Documents Grid mobile', () => {
hasText: 'My mocked document',
});
await expect(row.getByTestId('doc-title')).toHaveText('My mocked document');
await expect(
row.locator('[aria-describedby="doc-title"]').nth(0),
).toHaveText('My mocked document');
});
});
@@ -147,7 +149,7 @@ test.describe('Document grid item options', () => {
await page
.getByRole('button', {
name: 'Delete document',
name: 'Confirm deletion',
})
.click();
@@ -293,7 +295,7 @@ test.describe('Documents Grid', () => {
docs = result.results as SmallDoc[];
await expect(page.getByTestId('grid-loader')).toBeHidden();
await expect(page.locator('h2').getByText('All docs')).toBeVisible();
await expect(page.locator('h4').getByText('All docs')).toBeVisible();
const thead = page.getByTestId('docs-grid-header');
await expect(thead.getByText(/Name/i)).toBeVisible();
@@ -25,7 +25,7 @@ test.describe('Doc Header', () => {
'It is the card information about the document.',
);
const docTitle = card.getByRole('textbox', { name: 'Document title' });
const docTitle = card.getByRole('textbox', { name: 'doc title input' });
await expect(docTitle).toBeVisible();
await page.getByRole('button', { name: 'Share' }).click();
@@ -54,7 +54,7 @@ test.describe('Doc Header', () => {
test('it updates the title doc', async ({ page, browserName }) => {
await createDoc(page, 'doc-update', browserName, 1);
const docTitle = page.getByRole('textbox', { name: 'Document title' });
const docTitle = page.getByRole('textbox', { name: 'doc title input' });
await expect(docTitle).toBeVisible();
await docTitle.fill('Hello World');
await docTitle.blur();
@@ -66,7 +66,7 @@ test.describe('Doc Header', () => {
browserName,
}) => {
await createDoc(page, 'doc-update', browserName, 1);
const docTitle = page.getByRole('textbox', { name: 'Document title' });
const docTitle = page.getByRole('textbox', { name: 'doc title input' });
await expect(docTitle).toBeVisible();
await docTitle.fill('👍 Hello Emoji World');
await docTitle.blur();
@@ -100,7 +100,7 @@ test.describe('Doc Header', () => {
await page
.getByRole('button', {
name: 'Delete document',
name: 'Confirm deletion',
})
.click();
@@ -155,9 +155,7 @@ test.describe('Doc Header', () => {
await page.getByRole('button', { name: 'Share' }).click();
const shareModal = page.getByRole('dialog', {
name: 'Share modal content',
});
const shareModal = page.getByLabel('Share modal');
await expect(shareModal).toBeVisible();
await expect(page.getByText('Share the document')).toBeVisible();
@@ -228,27 +226,23 @@ test.describe('Doc Header', () => {
await page.getByRole('button', { name: 'Share' }).click();
const shareModal = page.getByRole('dialog', {
name: 'Share modal content',
});
await expect(shareModal).toBeVisible();
const shareModal = page.getByLabel('Share modal');
await expect(page.getByText('Share the document')).toBeVisible();
await expect(page.getByPlaceholder('Type a name or email')).toBeHidden();
const invitationCard = shareModal.getByLabel('List invitation card');
await expect(invitationCard).toBeVisible();
await expect(
invitationCard.getByText('test@invitation.test').first(),
).toBeVisible();
await expect(invitationCard.getByLabel('Document role text')).toBeVisible();
await expect(invitationCard.getByLabel('doc-role-text')).toBeVisible();
await expect(
invitationCard.getByRole('button', { name: 'more_horiz' }),
).toBeHidden();
const memberCard = shareModal.getByLabel('List members card');
await expect(memberCard.getByText('test@accesses.test')).toBeVisible();
await expect(memberCard.getByLabel('Document role text')).toBeVisible();
await expect(memberCard.getByLabel('doc-role-text')).toBeVisible();
await expect(
memberCard.getByRole('button', { name: 'more_horiz' }),
).toBeHidden();
@@ -300,18 +294,17 @@ test.describe('Doc Header', () => {
await expect(page.getByPlaceholder('Type a name or email')).toBeHidden();
const invitationCard = shareModal.getByLabel('List invitation card');
await expect(invitationCard).toBeVisible();
await expect(
invitationCard.getByText('test@invitation.test').first(),
).toBeVisible();
await expect(invitationCard.getByLabel('Document role text')).toBeVisible();
await expect(invitationCard.getByLabel('doc-role-text')).toBeVisible();
await expect(
invitationCard.getByRole('button', { name: 'more_horiz' }),
).toBeHidden();
const memberCard = shareModal.getByLabel('List members card');
await expect(memberCard.getByText('test@accesses.test')).toBeVisible();
await expect(memberCard.getByLabel('Document role text')).toBeVisible();
await expect(memberCard.getByLabel('doc-role-text')).toBeVisible();
await expect(
memberCard.getByRole('button', { name: 'more_horiz' }),
).toBeHidden();
@@ -321,6 +314,7 @@ test.describe('Doc Header', () => {
page,
browserName,
}) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
@@ -355,6 +349,7 @@ test.describe('Doc Header', () => {
});
test('It checks the copy as HTML button', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
@@ -389,6 +384,7 @@ test.describe('Doc Header', () => {
});
test('it checks the copy link button', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
@@ -585,10 +581,7 @@ test.describe('Documents Header mobile', () => {
await page.getByLabel('Open the document options').click();
await page.getByLabel('Share').click();
const shareModal = page.getByRole('dialog', {
name: 'Share modal content',
});
await expect(shareModal).toBeVisible();
await expect(page.getByLabel('Share modal')).toBeVisible();
await page.getByRole('button', { name: 'close' }).click();
await expect(page.getByLabel('Share modal')).toBeHidden();
});
@@ -52,12 +52,6 @@ test.describe('Inherited share accesses', () => {
await expect(docVisibilityCard.getByText('Connected')).toBeVisible();
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
await docVisibilityCard.getByText('Reading').click();
await page.getByRole('menuitem', { name: 'Editing' }).click();
await expect(docVisibilityCard.getByText('Reading')).toBeHidden();
await expect(docVisibilityCard.getByText('Editing')).toBeVisible();
// Verify inherited link
await docVisibilityCard.getByText('Connected').click();
await expect(
@@ -67,13 +61,17 @@ test.describe('Inherited share accesses', () => {
// Update child link
await page.getByRole('menuitem', { name: 'Public' }).click();
await docVisibilityCard.getByText('Reading').click();
await page.getByRole('menuitem', { name: 'Editing' }).click();
await expect(docVisibilityCard.getByText('Connected')).toBeHidden();
await expect(docVisibilityCard.getByText('Reading')).toBeHidden();
await expect(
docVisibilityCard.getByText('Public', {
exact: true,
}),
).toBeVisible();
await expect(docVisibilityCard.getByText('Editing')).toBeVisible();
await expect(
docVisibilityCard.getByText(
'The link sharing rules differ from the parent document',
@@ -15,7 +15,7 @@ test.describe('Document create member', () => {
});
test('it selects 2 users and 1 invitation', async ({ page, browserName }) => {
const inputFill = 'user.test';
const inputFill = 'user ';
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes(`/users/?q=${encodeURIComponent(inputFill)}`) &&
@@ -201,7 +201,7 @@ test.describe('Document create member', () => {
await page.getByLabel('Reader').click();
const moreActions = userInvitation.getByRole('button', {
name: 'Open invitation actions menu',
name: 'more_horiz',
});
await moreActions.click();
@@ -151,6 +151,7 @@ test.describe('Document list members', () => {
await expect(soloOwner).toBeVisible();
await list.click({
// eslint-disable-next-line playwright/no-force-option
force: true, // Force click to close the dropdown
});
const newUserEmail = await addNewMember(page, 0, 'Owner');
@@ -162,11 +163,13 @@ test.describe('Document list members', () => {
await currentUserRole.click();
await expect(soloOwner).toBeHidden();
await list.click({
// eslint-disable-next-line playwright/no-force-option
force: true, // Force click to close the dropdown
});
await newUserRoles.click();
await list.click({
// eslint-disable-next-line playwright/no-force-option
force: true, // Force click to close the dropdown
});
@@ -40,6 +40,7 @@ test.describe('Doc Routing', () => {
});
test('checks 404 on docs/[id] page', async ({ page }) => {
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(300);
await page.goto('/docs/some-unknown-doc');
@@ -60,39 +61,32 @@ test.describe('Doc Routing', () => {
await page.locator('.ProseMirror.bn-editor').fill('Hello World');
// Wait for the doc link (via its dynamic title) to be visible
const docLink = page.getByRole('link', { name: docTitle });
await expect(docLink).toBeVisible();
const responsePromise = page.route(
/.*\/documents\/.*\/$|users\/me\/$/,
async (route) => {
const request = route.request();
// Intercept GET/PATCH requests to return 401
await page.route(/.*\/documents\/.*\/$|users\/me\/$/, async (route) => {
const request = route.request();
if (
request.method().includes('PATCH') ||
request.method().includes('GET')
) {
await route.fulfill({
status: 401,
json: { detail: 'Log in to access the document' },
});
} else {
await route.continue();
}
});
// Explicitly wait for a 401 response after clicking
const wait401 = page.waitForResponse(
(resp) =>
resp.status() === 401 &&
/\/(documents\/[^/]+\/|users\/me\/)$/.test(resp.url()),
if (
request.method().includes('PATCH') ||
request.method().includes('GET')
) {
await route.fulfill({
status: 401,
json: {
detail: 'Log in to access the document',
},
});
} else {
await route.continue();
}
},
);
await docLink.click();
await wait401;
await page.getByRole('link', { name: '401-doc-parent' }).click();
await expect(page.getByText('Log in to access the document.')).toBeVisible({
timeout: 10000,
});
await responsePromise;
await expect(page.getByText('Log in to access the document')).toBeVisible();
});
});
@@ -33,7 +33,7 @@ test.describe('Document search', () => {
).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Search docs' }),
page.getByLabel('Search modal').getByText('search'),
).toBeVisible();
const inputSearch = page.getByPlaceholder('Type the name of a document');
@@ -79,7 +79,7 @@ test.describe('Document search', () => {
await page.keyboard.press('Control+k');
await expect(
page.getByRole('heading', { name: 'Search docs' }),
page.getByLabel('Search modal').getByText('search'),
).toBeVisible();
await page.keyboard.press('Escape');
@@ -173,13 +173,12 @@ test.describe('Document search', () => {
.getByRole('combobox', { name: 'Quick search input' })
.fill('sub page search');
// Expect to find the first and second docs in the results list
const resultsList = page.getByRole('listbox');
// Expect to find the first doc
await expect(
resultsList.getByRole('option', { name: firstDocTitle }),
page.getByRole('presentation').getByLabel(firstDocTitle),
).toBeVisible();
await expect(
resultsList.getByRole('option', { name: secondDocTitle }),
page.getByRole('presentation').getByLabel(secondDocTitle),
).toBeVisible();
await page.getByRole('button', { name: 'close' }).click();
@@ -196,15 +195,14 @@ test.describe('Document search', () => {
.fill('second');
// Now there is a sub page - expect to have the focus on the current doc
const updatedResultsList = page.getByRole('listbox');
await expect(
updatedResultsList.getByRole('option', { name: secondDocTitle }),
page.getByRole('presentation').getByLabel(secondDocTitle),
).toBeVisible();
await expect(
updatedResultsList.getByRole('option', { name: secondChildDocTitle }),
page.getByRole('presentation').getByLabel(secondChildDocTitle),
).toBeVisible();
await expect(
updatedResultsList.getByRole('option', { name: firstDocTitle }),
page.getByRole('presentation').getByLabel(firstDocTitle),
).toBeHidden();
});
});
@@ -1,3 +1,4 @@
/* eslint-disable playwright/no-conditional-in-test */
import { expect, test } from '@playwright/test';
import {
@@ -50,7 +51,7 @@ test.describe('Doc Tree', () => {
await expect(subPageItem).toBeVisible();
await subPageItem.click();
await verifyDocName(page, '');
const input = page.getByRole('textbox', { name: 'Document title' });
const input = page.getByRole('textbox', { name: 'doc title input' });
await input.click();
const [randomDocName] = randomName('doc-tree-test', browserName, 1);
await input.fill(randomDocName);
@@ -196,7 +197,7 @@ test.describe('Doc Tree', () => {
await page.getByText('Move to my docs').click();
await expect(
page.getByRole('textbox', { name: 'Document title' }),
page.getByRole('textbox', { name: 'doc title input' }),
).not.toHaveText(docChild);
const header = page.locator('header').first();
@@ -15,6 +15,7 @@ test.describe('Doc Visibility', () => {
});
test('It checks the copy link button', async ({ page, browserName }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(
browserName === 'webkit',
'navigator.clipboard is not working with webkit and playwright',
@@ -118,11 +119,8 @@ test.describe('Doc Visibility: Restricted', () => {
.click();
const otherBrowser = BROWSERS.find((b) => b !== browserName);
if (!otherBrowser) {
throw new Error('No alternative browser found');
}
await keyCloakSignIn(page, otherBrowser);
await keyCloakSignIn(page, otherBrowser!);
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
@@ -153,9 +151,6 @@ test.describe('Doc Visibility: Restricted', () => {
});
const otherBrowser = BROWSERS.find((b) => b !== browserName);
if (!otherBrowser) {
throw new Error('No alternative browser found');
}
const username = `user@${otherBrowser}.test`;
await inputSearch.fill(username);
await page.getByRole('option', { name: username }).click();
@@ -179,7 +174,7 @@ test.describe('Doc Visibility: Restricted', () => {
})
.click();
await keyCloakSignIn(page, otherBrowser);
await keyCloakSignIn(page, otherBrowser!);
await expect(page.getByTestId('header-logo-link')).toBeVisible();
@@ -454,10 +449,7 @@ test.describe('Doc Visibility: Authenticated', () => {
.click();
const otherBrowser = BROWSERS.find((b) => b !== browserName);
if (!otherBrowser) {
throw new Error('No alternative browser found');
}
await keyCloakSignIn(page, otherBrowser);
await keyCloakSignIn(page, otherBrowser!);
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
@@ -546,10 +538,7 @@ test.describe('Doc Visibility: Authenticated', () => {
.click();
const otherBrowser = BROWSERS.find((b) => b !== browserName);
if (!otherBrowser) {
throw new Error('No alternative browser found');
}
await keyCloakSignIn(page, otherBrowser);
await keyCloakSignIn(page, otherBrowser!);
await expect(page.getByTestId('header-logo-link')).toBeVisible({
timeout: 10000,
@@ -76,6 +76,7 @@ test.describe('Header', () => {
* La gaufre load a js file from a remote server,
* it takes some time to load the file and have the interaction available
*/
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1500);
await header
@@ -27,7 +27,7 @@ export const CONFIG = {
export const overrideConfig = async (
page: Page,
newConfig: { [_K in keyof typeof CONFIG]?: unknown },
newConfig: { [K in keyof typeof CONFIG]?: unknown },
) =>
await page.route('**/api/v1.0/config/', async (route) => {
const request = route.request();
@@ -101,9 +101,10 @@ export const createDoc = async (
waitUntil: 'networkidle',
});
const input = page.getByLabel('Document title');
const input = page.getByLabel('doc title input');
await expect(input).toBeVisible();
await expect(input).toHaveText('');
await input.click();
await input.fill(randomDocs[i]);
await input.blur();
@@ -119,11 +120,10 @@ export const verifyDocName = async (page: Page, docName: string) => {
timeout: 10000,
});
/*replace toHaveText with toContainText to handle cases where emojis or other characters might be added*/
try {
await expect(
page.getByRole('textbox', { name: 'Document title' }),
).toContainText(docName);
page.getByRole('textbox', { name: 'doc title input' }),
).toHaveText(docName);
} catch {
await expect(page.getByRole('heading', { name: docName })).toBeVisible();
}
@@ -172,7 +172,7 @@ export const goToGridDoc = async (
await expect(row).toBeVisible();
const docTitleContent = row.getByTestId('doc-title').first();
const docTitleContent = row.locator('[aria-describedby="doc-title"]').first();
const docTitle = await docTitleContent.textContent();
expect(docTitle).toBeDefined();
@@ -182,9 +182,9 @@ export const goToGridDoc = async (
};
export const updateDocTitle = async (page: Page, title: string) => {
const input = page.getByRole('textbox', { name: 'Document title' });
await expect(input).toHaveText('');
const input = page.getByLabel('doc title input');
await expect(input).toBeVisible();
await expect(input).toHaveText('');
await input.click();
await input.fill(title);
await input.click();
@@ -8,7 +8,7 @@ export const addNewMember = async (
page: Page,
index: number,
role: 'Administrator' | 'Owner' | 'Editor' | 'Reader',
fillText: string = 'user.test',
fillText: string = 'user ',
) => {
const responsePromiseSearchUser = page.waitForResponse(
(response) =>
-20
View File
@@ -1,20 +0,0 @@
import { defineConfig } from '@eslint/config-helpers';
import docsPlugin from 'eslint-plugin-docs';
const eslintConfig = defineConfig([
{
files: ['**/*.ts', '**/*.mjs'],
plugins: {
docs: docsPlugin,
},
extends: ['docs/playwright'],
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
project: ['./tsconfig.json'],
},
},
},
]);
export default eslintConfig;
+4 -8
View File
@@ -1,12 +1,9 @@
{
"name": "app-e2e",
"version": "3.7.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
"version": "3.6.0",
"private": true,
"scripts": {
"lint": "eslint",
"lint": "eslint . --ext .ts",
"install-playwright": "playwright install --with-deps",
"test": "playwright test",
"test:ui": "yarn test --ui",
@@ -18,12 +15,11 @@
"@playwright/test": "1.55.0",
"@types/node": "*",
"@types/pdf-parse": "1.1.5",
"eslint-plugin-docs": "*",
"eslint-config-impress": "*",
"typescript": "*"
},
"dependencies": {
"convert-stream": "1.0.2",
"pdf-parse": "1.1.1"
},
"packageManager": "yarn@1.22.22"
}
}
+2 -2
View File
@@ -12,8 +12,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true
"incremental": true,
},
"include": ["**/*.ts", "**/*.d.ts", "**/*.mjs"],
"include": ["**/*.ts", "**/*.d.ts"],
"exclude": ["node_modules"]
}
+1 -1
View File
@@ -1,5 +1,5 @@
declare module 'convert-stream' {
export function toBuffer(
_readableStream: NodeJS.ReadableStream,
readableStream: NodeJS.ReadableStream,
): Promise<Buffer>;
}
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
root: true,
extends: ['impress/next'],
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
settings: {
next: {
rootDir: __dirname,
},
},
ignorePatterns: ['node_modules', '.eslintrc.js', 'service-worker.js'],
};
@@ -1,24 +0,0 @@
import { defineConfig } from '@eslint/config-helpers';
import docsPlugin from 'eslint-plugin-docs';
const eslintConfig = defineConfig([
{
plugins: {
docs: docsPlugin,
},
extends: ['docs/next'],
languageOptions: {
parserOptions: {
tsconfigRootDir: import.meta.dirname,
project: ['./tsconfig.json'],
},
},
settings: {
next: {
rootDir: import.meta.dirname,
},
},
},
]);
export default eslintConfig;
-1
View File
@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
+14 -18
View File
@@ -1,9 +1,6 @@
{
"name": "app-impress",
"version": "3.7.0",
"repository": "https://github.com/suitenumerique/docs",
"author": "DINUM",
"license": "MIT",
"version": "3.6.0",
"private": true,
"scripts": {
"dev": "next dev",
@@ -36,22 +33,22 @@
"@hocuspocus/provider": "2.15.2",
"@openfun/cunningham-react": "3.2.3",
"@react-pdf/renderer": "4.3.0",
"@sentry/nextjs": "10.11.0",
"@tanstack/react-query": "5.87.4",
"@sentry/nextjs": "10.8.0",
"@tanstack/react-query": "5.85.6",
"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",
"emoji-regex": "10.5.0",
"i18next": "25.5.2",
"emoji-regex": "10.4.0",
"i18next": "25.4.2",
"i18next-browser-languagedetector": "8.2.0",
"idb": "8.0.3",
"lodash": "4.17.21",
"luxon": "3.7.2",
"next": "15.5.3",
"posthog-js": "1.264.2",
"luxon": "3.7.1",
"next": "15.4.7",
"posthog-js": "1.261.0",
"react": "*",
"react-aria-components": "1.12.1",
"react-dom": "*",
@@ -59,14 +56,14 @@
"react-intersection-observer": "9.16.0",
"react-select": "5.10.2",
"styled-components": "6.1.19",
"use-debounce": "10.0.6",
"use-debounce": "10.0.5",
"y-protocols": "1.0.6",
"yjs": "*",
"zustand": "5.0.8"
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.87.4",
"@tanstack/react-query-devtools": "5.85.6",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.8.0",
"@testing-library/react": "16.3.0",
@@ -78,13 +75,13 @@
"@types/react-dom": "*",
"@vitejs/plugin-react": "5.0.2",
"cross-env": "10.0.0",
"dotenv": "17.2.2",
"eslint-plugin-docs": "*",
"dotenv": "17.2.1",
"eslint-config-impress": "*",
"fetch-mock": "9.11.0",
"jsdom": "26.1.0",
"node-fetch": "2.7.0",
"prettier": "3.6.2",
"stylelint": "16.24.0",
"stylelint": "16.23.1",
"stylelint-config-standard": "39.0.0",
"stylelint-prettier": "5.0.3",
"typescript": "*",
@@ -92,6 +89,5 @@
"vitest": "3.2.4",
"webpack": "5.101.3",
"workbox-webpack-plugin": "7.1.0"
},
"packageManager": "yarn@1.22.22"
}
}
@@ -2,8 +2,8 @@ import { Button, Modal, ModalSize } from '@openfun/cunningham-react';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { Box } from '../Box';
import { Text } from '../Text';
import { Box } from './Box';
import { Text } from './Text';
export type AlertModalProps = {
description: ReactNode;
@@ -30,23 +30,15 @@ export const AlertModal = ({
isOpen={isOpen}
size={ModalSize.MEDIUM}
onClose={onClose}
aria-describedby="alert-modal-title"
title={
<Text
$size="h6"
as="h1"
$margin="0"
id="alert-modal-title"
$align="flex-start"
$variation="1000"
>
<Text $size="h6" $align="flex-start" $variation="1000">
{title}
</Text>
}
rightActions={
<>
<Button
aria-label={`${t('Cancel')} - ${title}`}
aria-label={t('Close the modal')}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -63,11 +55,12 @@ export const AlertModal = ({
</>
}
>
<Box className="--docs--alert-modal">
<Box
aria-label={t('Confirmation button')}
className="--docs--alert-modal"
>
<Box>
<Text $variation="600" as="p">
{description}
</Text>
<Text $variation="600">{description}</Text>
</Box>
</Box>
</Modal>
@@ -28,18 +28,17 @@ const StyledButton = styled(Button)<StyledButtonProps>`
border: none;
background: none;
outline: none;
transition: all 0.2s ease-in-out;
font-weight: 500;
font-size: 0.938rem;
padding: 0;
${({ $css }) => $css};
&:hover {
background-color: var(
--c--components--button--primary-text--background--color-hover
);
}
&:focus-visible {
box-shadow: 0 0 0 2px var(--c--theme--colors--primary-400);
outline: 2px solid var(--c--theme--colors--primary-500);
outline-offset: 2px;
border-radius: 4px;
transition: none;
}
`;
@@ -110,6 +110,7 @@ export const DropdownMenu = ({
$direction="row"
$align="center"
$position="relative"
aria-controls="menu"
>
<Box>{children}</Box>
<Icon
@@ -124,7 +125,9 @@ export const DropdownMenu = ({
/>
</Box>
) : (
<Box ref={blockButtonRef}>{children}</Box>
<Box ref={blockButtonRef} aria-controls="menu">
{children}
</Box>
)
}
>
@@ -204,13 +207,14 @@ export const DropdownMenu = ({
}
&:focus-visible {
outline: 2px solid var(--c--theme--colors--primary-400);
outline: 2px solid var(--c--theme--colors--primary-500);
outline-offset: -2px;
background-color: var(--c--theme--colors--greyscale-050);
}
${isFocused &&
css`
outline: 2px solid var(--c--theme--colors--primary-500);
outline-offset: -2px;
background-color: var(--c--theme--colors--greyscale-050);
`}
@@ -227,7 +231,6 @@ export const DropdownMenu = ({
$theme="greyscale"
$variation={isDisabled ? '400' : '1000'}
iconName={option.icon}
aria-hidden="true"
/>
)}
<Text $variation={isDisabled ? '400' : '1000'}>
@@ -236,12 +239,7 @@ export const DropdownMenu = ({
</Box>
{(option.isSelected ||
selectedValues?.includes(option.value ?? '')) && (
<Icon
iconName="check"
$size="20px"
$theme="greyscale"
aria-hidden="true"
/>
<Icon iconName="check" $size="20px" $theme="greyscale" />
)}
</BoxButton>
{option.showSeparator && (
@@ -32,7 +32,7 @@ export const useDropdownKeyboardNav = ({
.filter((index) => index !== -1);
switch (event.key) {
case 'ArrowDown': {
case 'ArrowDown':
event.preventDefault();
const nextIndex =
focusedIndex < enabledIndices.length - 1 ? focusedIndex + 1 : 0;
@@ -40,9 +40,8 @@ export const useDropdownKeyboardNav = ({
setFocusedIndex(nextIndex);
menuItemRefs.current[nextEnabledIndex]?.focus();
break;
}
case 'ArrowUp': {
case 'ArrowUp':
event.preventDefault();
const prevIndex =
focusedIndex > 0 ? focusedIndex - 1 : enabledIndices.length - 1;
@@ -50,10 +49,9 @@ export const useDropdownKeyboardNav = ({
setFocusedIndex(prevIndex);
menuItemRefs.current[prevEnabledIndex]?.focus();
break;
}
case 'Enter':
case ' ': {
case ' ':
event.preventDefault();
if (focusedIndex >= 0 && focusedIndex < enabledIndices.length) {
const selectedOptionIndex = enabledIndices[focusedIndex];
@@ -64,7 +62,6 @@ export const useDropdownKeyboardNav = ({
}
}
break;
}
case 'Escape':
event.preventDefault();
@@ -1,4 +1,4 @@
export * from './modal/AlertModal';
export * from './AlertModal';
export * from './Box';
export * from './BoxButton';
export * from './Card';
@@ -9,7 +9,7 @@ export * from './Icon';
export * from './InfiniteScroll';
export * from './Link';
export * from './Loading';
export * from './modal/SideModal';
export * from './SideModal';
export * from './separators';
export * from './Text';
export * from './TextErrors';
@@ -1,22 +0,0 @@
import { Button, type ButtonProps } from '@openfun/cunningham-react';
import React from 'react';
import { Box } from '@/components';
const ButtonCloseModal = (props: ButtonProps) => {
return (
<Button
type="button"
size="small"
color="primary-text"
icon={
<Box as="span" aria-hidden="true" className="material-icons-filled">
close
</Box>
}
{...props}
/>
);
};
export default ButtonCloseModal;
@@ -55,6 +55,7 @@ export const QuickSearchInput = ({
</div>
)}
<Command.Input
/* eslint-disable-next-line jsx-a11y/no-autofocus */
autoFocus={true}
aria-label={t('Quick search input')}
onClick={(e) => {
@@ -64,7 +65,6 @@ export const QuickSearchInput = ({
role="combobox"
placeholder={placeholder ?? t('Search')}
onValueChange={onFilter}
maxLength={254}
/>
</Box>
{separator && <HorizontalSeparator $withPadding={false} />}
+2
View File
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
declare module '*.svg' {
import * as React from 'react';
@@ -2,8 +2,7 @@ import { Button } from '@openfun/cunningham-react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, BoxButton } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { BoxButton } from '@/components';
import ProConnectImg from '../assets/button-proconnect.svg';
import { useAuth } from '../hooks';
@@ -12,7 +11,6 @@ import { gotoLogin, gotoLogout } from '../utils';
export const ButtonLogin = () => {
const { t } = useTranslation();
const { authenticated } = useAuth();
const { colorsTokens } = useCunninghamTheme();
if (!authenticated) {
return (
@@ -28,23 +26,14 @@ export const ButtonLogin = () => {
}
return (
<Box
$css={css`
.--docs--button-logout:focus-visible {
box-shadow: 0 0 0 2px ${colorsTokens['primary-400']} !important;
border-radius: 4px;
}
`}
<Button
onClick={gotoLogout}
color="primary-text"
aria-label={t('Logout')}
className="--docs--button-logout"
>
<Button
onClick={gotoLogout}
color="primary-text"
aria-label={t('Logout')}
className="--docs--button-logout"
>
{t('Logout')}
</Button>
</Box>
{t('Logout')}
</Button>
);
};
@@ -44,6 +44,7 @@ import {
} from './custom-inline-content';
import XLMultiColumn from './xl-multi-column';
const multiColumnDropCursor = XLMultiColumn?.multiColumnDropCursor;
const multiColumnLocales = XLMultiColumn?.locales;
const withMultiColumn = XLMultiColumn?.withMultiColumn;
@@ -156,6 +157,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
},
uploadFile,
schema: blockNoteSchema,
dropCursor: multiColumnDropCursor,
},
[collabName, lang, provider, uploadFile],
);
@@ -205,7 +205,7 @@ type ItemProps = Omit<ItemDefault, 'onClick'> & {
onClick: (e: React.MouseEvent) => void;
};
interface AIMenuItemTransformProps {
interface AIMenuItemTransform {
action: AITransformActions;
docId: string;
icon?: ReactNode;
@@ -216,7 +216,7 @@ const AIMenuItemTransform = ({
action,
children,
icon,
}: PropsWithChildren<AIMenuItemTransformProps>) => {
}: PropsWithChildren<AIMenuItemTransform>) => {
const { mutateAsync: requestAI, isPending } = useDocAITransform();
const editor = useBlockNoteEditor();
@@ -244,7 +244,7 @@ const AIMenuItemTransform = ({
);
};
interface AIMenuItemTranslateProps {
interface AIMenuItemTranslate {
language: string;
docId: string;
icon?: ReactNode;
@@ -255,7 +255,7 @@ const AIMenuItemTranslate = ({
docId,
icon,
language,
}: PropsWithChildren<AIMenuItemTranslateProps>) => {
}: PropsWithChildren<AIMenuItemTranslate>) => {
const { mutateAsync: requestAI, isPending } = useDocAITranslate();
const editor = useBlockNoteEditor();
@@ -19,11 +19,10 @@ export const ModalConfirmDownloadUnsafe = ({
isOpen
closeOnClickOutside
onClose={() => onClose()}
aria-describedby="modal-confirm-download-unsafe-title"
rightActions={
<>
<Button
aria-label={t('Cancel the download')}
aria-label={t('Close the modal')}
color="secondary"
onClick={() => onClose()}
>
@@ -32,7 +31,6 @@ export const ModalConfirmDownloadUnsafe = ({
<Button
aria-label={t('Download')}
color="danger"
data-testid="modal-download-unsafe-button"
onClick={() => {
if (onConfirm) {
void onConfirm();
@@ -47,14 +45,11 @@ export const ModalConfirmDownloadUnsafe = ({
size={ModalSize.SMALL}
title={
<Text
as="h1"
id="modal-confirm-download-unsafe-title"
$gap="0.7rem"
$size="h6"
$align="flex-start"
$variation="1000"
$direction="row"
$margin="0"
>
<Icon iconName="warning" $theme="warning" />
{t('Warning')}
@@ -7,12 +7,14 @@ import { Box } from '@/components';
interface EmojiPickerProps {
emojiData: EmojiMartData;
categories: string[];
onClickOutside: () => void;
onEmojiSelect: ({ native }: { native: string }) => void;
}
export const EmojiPicker = ({
emojiData,
categories,
onClickOutside,
onEmojiSelect,
}: EmojiPickerProps) => {
@@ -22,11 +24,14 @@ export const EmojiPicker = ({
<Box $position="absolute" $zIndex={1000} $margin="2rem 0 0 0">
<Picker
data={emojiData}
categories={categories}
locale={i18n.resolvedLanguage}
navPosition="none"
onClickOutside={onClickOutside}
onEmojiSelect={onEmojiSelect}
previewPosition="none"
skinTonePosition="none"
theme="light"
/>
</Box>
);
@@ -10,7 +10,7 @@ import { Box, BoxButton, Icon } from '@/components';
import { DocsBlockNoteEditor } from '../../types';
import { EmojiPicker } from '../EmojiPicker';
import emojidata from './initEmojiCallout';
import InitEmojiCallout from './initEmojiCallout';
export const CalloutBlock = createReactBlockSpec(
{
@@ -79,7 +79,8 @@ export const CalloutBlock = createReactBlockSpec(
{openEmojiPicker && (
<EmojiPicker
emojiData={emojidata}
emojiData={InitEmojiCallout.emojidata}
categories={InitEmojiCallout.calloutCategories}
onClickOutside={onClickOutside}
onEmojiSelect={onEmojiSelect}
/>
@@ -56,4 +56,21 @@ if (!emojidata.categories.some((c) => c.id === CALLOUT_ID)) {
void init({ data: emojidata });
export default emojidata;
const calloutCategories = [
'callout',
'people',
'nature',
'foods',
'activity',
'places',
'flags',
'objects',
'symbols',
];
const calloutEmojiData = {
emojidata,
calloutCategories,
};
export default calloutEmojiData;
@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { createReactInlineContentSpec } from '@blocknote/react';
import { TFunction } from 'i18next';
@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/rules-of-hooks */
import {
PartialCustomInlineContentFromConfig,
StyleSchema,
@@ -7,19 +7,6 @@ export const cssEditor = (readonly: boolean) => css`
height: 100%;
padding-bottom: 2rem;
/**
* WCAG Accessibility contrast fixes for BlockNote editor
*/
.bn-block-content[data-is-empty-and-focused][data-content-type='paragraph']
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
color: #767676 !important;
font-weight: 400;
}
.bn-side-menu .mantine-UnstyledButton-root svg {
color: #767676 !important;
}
img.bn-visual-media[src*='-unsafe'] {
pointer-events: none;
}
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/alt-text */
import { DefaultProps } from '@blocknote/core';
import { Image, Text, View } from '@react-pdf/renderer';
@@ -16,7 +16,6 @@ import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Text } from '@/components';
import ButtonCloseModal from '@/components/modal/ButtonCloseModal';
import { useEditorStore } from '@/docs/doc-editor';
import { Doc, useTrans } from '@/docs/doc-management';
@@ -132,12 +131,10 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
isOpen
closeOnClickOutside
onClose={() => onClose()}
hideCloseButton
aria-describedby="modal-export-title"
rightActions={
<>
<Button
aria-label={t('Cancel the download')}
aria-label={t('Close the modal')}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -146,7 +143,6 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
{t('Cancel')}
</Button>
<Button
data-testid="doc-export-download-button"
aria-label={t('Download')}
color="primary"
fullWidth
@@ -159,29 +155,9 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
}
size={ModalSize.MEDIUM}
title={
<Box
$direction="row"
$justify="space-between"
$align="center"
$width="100%"
>
<Text
as="h1"
$margin="0"
id="modal-export-title"
$size="h6"
$variation="1000"
$align="flex-start"
data-testid="modal-export-title"
>
{t('Download')}
</Text>
<ButtonCloseModal
aria-label={t('Close the download modal')}
onClick={() => onClose()}
disabled={isExporting}
/>
</Box>
<Text $size="h6" $variation="1000" $align="flex-start">
{t('Download')}
</Text>
}
>
<Box
@@ -190,7 +166,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
$gap="1rem"
className="--docs--modal-export-content"
>
<Text $variation="600" $size="sm" as="p">
<Text $variation="600" $size="sm">
{t('Download your document in a .docx or .pdf format.')}
</Text>
<Select
@@ -1,3 +1,4 @@
/* eslint-disable jsx-a11y/alt-text */
import { Image, Link, Text } from '@react-pdf/renderer';
import DocSelectedIcon from '../assets/doc-selected.png';
@@ -1,3 +1,5 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
import { Tooltip } from '@openfun/cunningham-react';
import React, { useCallback, useEffect, useState } from 'react';
@@ -105,7 +107,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
}, [doc]);
return (
<Tooltip content={t('Rename')} aria-hidden={true} placement="top">
<Tooltip content={t('Rename')} placement="top">
<Box
as="span"
role="textbox"
@@ -114,8 +116,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
defaultValue={titleDisplay || undefined}
onKeyDownCapture={handleKeyDown}
suppressContentEditableWarning={true}
aria-label={`${t('Document title')}`}
aria-multiline={false}
aria-label="doc title input"
onBlurCapture={(event) =>
handleTitleSubmit(event.target.textContent || '')
}
@@ -215,7 +215,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
>
<Button
color="tertiary"
aria-label={t('Share button')}
aria-label="Share button"
icon={
<Icon iconName="group" $theme="primary" $variation="800" />
}
@@ -233,15 +233,9 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
{!isSmallMobile && ModalExport && (
<Button
data-testid="doc-open-modal-download-button"
color="tertiary-text"
icon={
<Icon
iconName="download"
$theme="primary"
$variation="800"
aria-hidden={true}
/>
<Icon iconName="download" $theme="primary" $variation="800" />
}
onClick={() => {
setIsModalExportOpen(true);
@@ -250,9 +244,8 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
aria-label={t('Export the document')}
/>
)}
<DropdownMenu options={options} label={t('Open the document options')}>
<DropdownMenu options={options}>
<IconOptions
aria-hidden="true"
isHorizontal
$theme="primary"
$padding={{ all: 'xs' }}
@@ -268,6 +261,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
`
: ''}
`}
aria-label={t('Open the document options')}
/>
</DropdownMenu>
</Box>
@@ -65,20 +65,24 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) {
return useMutation<DuplicateDocResponse, APIError, DuplicateDocParams>({
mutationFn: async (variables) => {
// Save the document if we can first, to ensure the latest state is duplicated
const canSave =
variables.canSave &&
provider &&
provider.document.guid === variables.docId;
try {
// Save the document if we can first, to ensure the latest state is duplicated
if (
variables.canSave &&
provider &&
provider.document.guid === variables.docId
) {
await updateDoc({
id: variables.docId,
content: toBase64(Y.encodeStateAsUpdate(provider.document)),
});
}
if (canSave) {
await updateDoc({
id: variables.docId,
content: toBase64(Y.encodeStateAsUpdate(provider.document)),
});
return await duplicateDoc(variables);
} catch (error) {
// If save fails, throw the error to prevent duplication
throw error;
}
return await duplicateDoc(variables);
},
onSuccess: (data, variables, context) => {
void queryClient.resetQueries({
@@ -6,8 +6,8 @@ import { APIError, errorCauses, fetchAPI } from '@/api';
import { Doc, KEY_DOC } from '@/docs/doc-management';
import { useBroadcastStore } from '@/stores';
export type UpdateDocLinkParams = Pick<Doc, 'id' | 'link_reach'> &
Partial<Pick<Doc, 'link_role'>>;
export type UpdateDocLinkParams = Pick<Doc, 'id'> &
Partial<Pick<Doc, 'link_role' | 'link_reach'>>;
export const updateDocLink = async ({
id,
@@ -10,7 +10,6 @@ import { useRouter } from 'next/router';
import { Trans, useTranslation } from 'react-i18next';
import { Box, Text, TextErrors } from '@/components';
import ButtonCloseModal from '@/components/modal/ButtonCloseModal';
import { useRemoveDoc } from '../api/useRemoveDoc';
import { Doc } from '../types';
@@ -54,13 +53,11 @@ export const ModalRemoveDoc = ({
<Modal
isOpen
closeOnClickOutside
hideCloseButton
onClose={() => onClose()}
aria-describedby="modal-remove-doc-title"
rightActions={
<>
<Button
aria-label={t('Cancel the deletion')}
aria-label={t('Close the modal')}
color="secondary"
fullWidth
onClick={() => onClose()}
@@ -68,7 +65,7 @@ export const ModalRemoveDoc = ({
{t('Cancel')}
</Button>
<Button
aria-label={t('Delete document')}
aria-label={t('Confirm deletion')}
color="danger"
fullWidth
onClick={() =>
@@ -83,32 +80,23 @@ export const ModalRemoveDoc = ({
}
size={ModalSize.MEDIUM}
title={
<Box
$direction="row"
$justify="space-between"
$align="center"
$width="100%"
<Text
$size="h6"
as="h6"
$margin={{ all: '0' }}
$align="flex-start"
$variation="1000"
>
<Text
$size="h6"
as="h1"
id="modal-remove-doc-title"
$margin="0"
$align="flex-start"
$variation="1000"
>
{t('Delete a doc')}
</Text>
<ButtonCloseModal
aria-label={t('Close the delete modal')}
onClick={() => onClose()}
/>
</Box>
{t('Delete a doc')}
</Text>
}
>
<Box className="--docs--modal-remove-doc">
<Box
aria-label={t('Content modal to delete document')}
className="--docs--modal-remove-doc"
>
{!isError && (
<Text $size="sm" $variation="600" $display="inline-block" as="p">
<Text $size="sm" $variation="600" $display="inline-block">
<Trans t={t}>
This document and <strong>any sub-documents</strong> will be
permanently deleted. This action is irreversible.
@@ -82,11 +82,12 @@ export const SimpleDocItem = ({
</Box>
<Box $justify="center" $overflow="auto">
<Text
aria-describedby="doc-title"
aria-label={displayTitle}
$size="sm"
$variation="1000"
$weight="500"
$css={ItemTextCss}
data-testid="doc-title"
>
{displayTitle}
</Text>
@@ -5,8 +5,7 @@ import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDebouncedCallback } from 'use-debounce';
import { Box, Text } from '@/components';
import ButtonCloseModal from '@/components/modal/ButtonCloseModal';
import { Box } from '@/components';
import { QuickSearch } from '@/components/quick-search';
import { Doc, useDocUtils } from '@/docs/doc-management';
import { useResponsiveStore } from '@/stores';
@@ -64,8 +63,6 @@ const DocSearchModalGlobal = ({
{...modalProps}
closeOnClickOutside
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
hideCloseButton
aria-describedby="doc-search-modal-title"
>
<Box
aria-label={t('Search modal')}
@@ -73,22 +70,6 @@ const DocSearchModalGlobal = ({
$justify="space-between"
className="--docs--doc-search-modal"
>
<Text
as="h1"
$margin="0"
id="doc-search-modal-title"
className="sr-only"
>
{t('Search docs')}
</Text>
<Box $position="absolute" $css="top: 12px; right: 12px;">
<ButtonCloseModal
aria-label={t('Close the search modal')}
onClick={modalProps.onClose}
size="small"
color="primary-text"
/>
</Box>
<QuickSearch
placeholder={t('Type the name of a document')}
loading={loading}

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