Compare commits
52 Commits
v4.8.2-preprod
...
v4.8.4
| Author | SHA1 | Date | |
|---|---|---|---|
| e7cbe24f3d | |||
| acb20a0d26 | |||
| cbe6a67704 | |||
| f91223fe4a | |||
| 330096eb47 | |||
| ff995c6cd9 | |||
| 2e4a1b8ff9 | |||
| 004d637c8b | |||
| 8a0330a30f | |||
| 677392b89b | |||
| b8e1d12aea | |||
| 525d8c8417 | |||
| c886cbb41d | |||
| 98f3ca2763 | |||
| fb92a43755 | |||
| 03fd1fe50e | |||
| fc803226ac | |||
| fb725edda3 | |||
| 6838b387a2 | |||
| 87f570582f | |||
| 37f56fcc22 | |||
| 19aa3a36bc | |||
| 0d09f761dc | |||
| ce5f9a1417 | |||
| 83a24c3796 | |||
| 4a269e6b0e | |||
| d9d7b70b71 | |||
| a4326366c2 | |||
| 1d7b57e03d | |||
| c4c6c22e42 | |||
| 10a8eccc71 | |||
| 728332f8f7 | |||
| 487b95c207 | |||
| d23b38e478 | |||
| d6333c9b81 | |||
| 03b6c6a206 | |||
| aadabf8d3c | |||
| 2a708d6e46 | |||
| b47c730e19 | |||
| cef83067e6 | |||
| 4cabfcc921 | |||
| b8d4b0a044 | |||
| 71c4d2921b | |||
| d1636dee13 | |||
| bf93640af8 | |||
| da79c310ae | |||
| 99c486571d | |||
| cdf3161869 | |||
| ef108227b3 | |||
| 9991820cb1 | |||
| 2801ece358 | |||
| 0b37996899 |
+59
-6
@@ -6,12 +6,63 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v4.8.4] - 2026-03-25
|
||||
|
||||
### Added
|
||||
|
||||
- 🚸(frontend) hint min char search users #2064
|
||||
|
||||
### Changed
|
||||
|
||||
- 💄(frontend) improve comments highlights #1961
|
||||
- ♿️(frontend) improve BoxButton a11y and native button semantics #2103
|
||||
- ♿️(frontend) improve language picker accessibility #2069
|
||||
- ♿️(frontend) add aria-hidden to decorative icons in dropdown menu #2093
|
||||
- 🐛(backend) move lock table closer to the insert operation targeted
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🐛(y-provider) destroy Y.Doc instances after each convert request #2129
|
||||
- 🐛(backend) remove deleted sub documents in favorite_list endpoint #2083
|
||||
|
||||
## [v4.8.3] - 2026-03-23
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) improve version history list accessibility #2033
|
||||
- ♿(frontend) focus skip link on headings and skip grid dropzone #1983
|
||||
- ♿️(frontend) add sr-only format to export download button #2088
|
||||
- ♿️(frontend) announce formatting shortcuts for screen readers #2070
|
||||
- ✨(frontend) add markdown copy icon for Copy as Markdown option #2096
|
||||
- ♻️(backend) skip saving in database a document when payload is empty #2062
|
||||
- ♻️(frontend) refacto Version modal to fit with the design system #2091
|
||||
- ⚡️(frontend) add debounce WebSocket reconnect #2104
|
||||
|
||||
### Fixed
|
||||
|
||||
- ♿️(frontend) fix more options menu feedback for screen readers #2071
|
||||
- ♿️(frontend) fix more options menu feedback for screen readers #2071
|
||||
- 💫(frontend) fix the help button to the bottom in tree #2073
|
||||
- ♿️(frontend) fix aria-labels for table of contents #2065
|
||||
- 🐛(backend) allow using search endpoint without refresh token enabled #2097
|
||||
- 🐛(frontend) fix close panel when click on subdoc #2094
|
||||
- 🐛(frontend) fix leftpanel button in doc version #9238
|
||||
- 🐛(y-provider) fix loop when no cookies #2101
|
||||
|
||||
## [v4.8.2] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- ✨(backend) add resource server api #1923
|
||||
- ✨(frontend) activate Find search #1834
|
||||
- ✨ handle searching on subdocuments #1834
|
||||
- ✨(backend) add search feature flags #1897
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) ensure doc title is h1 for accessibility #2006
|
||||
- ♿️(frontend) add nb accesses in share button aria-label #2017
|
||||
- ✨(backend) improve fallback logic on search endpoint #1834
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -25,6 +76,11 @@ and this project adheres to
|
||||
- 🐛(backend) stop using add_sibling method to create sandbox document #2084
|
||||
- 🐛(backend) duplicate a document as last-sibling #2084
|
||||
|
||||
### Removed
|
||||
|
||||
- 🔥(api) remove `documents/<document_id>/descendants/` endpoint #1834
|
||||
- 🔥(api) remove pagination on `documents/search/` endpoint #1834
|
||||
|
||||
## [v4.8.1] - 2026-03-17
|
||||
|
||||
### Added
|
||||
@@ -41,7 +97,6 @@ and this project adheres to
|
||||
|
||||
- ✨(backend) add a is_first_connection flag to the User model #1938
|
||||
- ✨(frontend) add onboarding modal with help menu button #1868
|
||||
- ✨(backend) add resource server api #1923
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -127,16 +182,12 @@ and this project adheres to
|
||||
- ✨(frontend) Add stat for Crisp #1824
|
||||
- ✨(auth) add silent login #1690
|
||||
- 🔧(project) add DJANGO_EMAIL_URL_APP environment variable #1825
|
||||
- ✨(frontend) activate Find search #1834
|
||||
- ✨ handle searching on subdocuments #1834
|
||||
- ✨(backend) add search feature flags #1897
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿(frontend) improve accessibility:
|
||||
- ♿️(frontend) fix subdoc opening and emoji pick focus #1745
|
||||
- ✨(backend) add field for button label in email template #1817
|
||||
- ✨(backend) improve fallback logic on search endpoint #1834
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1141,7 +1192,9 @@ 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/v4.8.2...main
|
||||
[unreleased]: https://github.com/suitenumerique/docs/compare/v4.8.4...main
|
||||
[v4.8.4]: https://github.com/suitenumerique/docs/releases/v4.8.4
|
||||
[v4.8.3]: https://github.com/suitenumerique/docs/releases/v4.8.3
|
||||
[v4.8.2]: https://github.com/suitenumerique/docs/releases/v4.8.2
|
||||
[v4.8.1]: https://github.com/suitenumerique/docs/releases/v4.8.1
|
||||
[v4.8.0]: https://github.com/suitenumerique/docs/releases/v4.8.0
|
||||
|
||||
@@ -61,8 +61,8 @@ OIDC_RS_AUDIENCE_CLAIM="client_id" # The claim used to identify the audience
|
||||
OIDC_RS_ALLOWED_AUDIENCES=""
|
||||
|
||||
# Store OIDC tokens in the session. Needed by search/ endpoint.
|
||||
OIDC_STORE_ACCESS_TOKEN=True
|
||||
OIDC_STORE_REFRESH_TOKEN=True # Store the encrypted refresh token in the session.
|
||||
# OIDC_STORE_ACCESS_TOKEN=True
|
||||
# OIDC_STORE_REFRESH_TOKEN=True # Store the encrypted refresh token in the session.
|
||||
|
||||
# Must be a valid Fernet key (32 url-safe base64-encoded bytes)
|
||||
# To create one, use the bin/fernetkey command.
|
||||
|
||||
@@ -60,10 +60,13 @@
|
||||
"groupName": "ignored js dependencies",
|
||||
"matchManagers": ["npm"],
|
||||
"matchPackageNames": [
|
||||
"@react-pdf/renderer",
|
||||
"fetch-mock",
|
||||
"node",
|
||||
"node-fetch",
|
||||
"react-resizable-panels",
|
||||
"stylelint",
|
||||
"stylelint-config-standard",
|
||||
"workbox-webpack-plugin"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -300,6 +300,15 @@ class DocumentSerializer(ListDocumentSerializer):
|
||||
|
||||
return file
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""
|
||||
When no data is sent on the update, skip making the update in the database and return
|
||||
directly the instance unchanged.
|
||||
"""
|
||||
if not validated_data:
|
||||
return instance # No data provided, skip the update
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
def save(self, **kwargs):
|
||||
"""
|
||||
Process the content field to extract attachment keys and update the document's
|
||||
|
||||
@@ -6,8 +6,10 @@ from abc import ABC, abstractmethod
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.files.storage import default_storage
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
import botocore
|
||||
from lasuite.oidc_login.decorators import refresh_oidc_access_token
|
||||
from rest_framework.throttling import BaseThrottle
|
||||
|
||||
|
||||
@@ -91,6 +93,19 @@ def generate_s3_authorization_headers(key):
|
||||
return request
|
||||
|
||||
|
||||
def conditional_refresh_oidc_token(func):
|
||||
"""
|
||||
Conditionally apply refresh_oidc_access_token decorator.
|
||||
|
||||
The decorator is only applied if OIDC_STORE_REFRESH_TOKEN is True, meaning
|
||||
we can actually refresh something. Broader settings checks are done in settings.py.
|
||||
"""
|
||||
if settings.OIDC_STORE_REFRESH_TOKEN:
|
||||
return method_decorator(refresh_oidc_access_token)(func)
|
||||
|
||||
return func
|
||||
|
||||
|
||||
class AIBaseRateThrottle(BaseThrottle, ABC):
|
||||
"""Base throttle class for AI-related rate limiting with backoff."""
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ from django.db.models.functions import Greatest, Left, Length
|
||||
from django.http import Http404, StreamingHttpResponse
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import content_disposition_header
|
||||
from django.utils.text import capfirst, slugify
|
||||
@@ -38,7 +37,6 @@ from botocore.exceptions import ClientError
|
||||
from csp.constants import NONE
|
||||
from csp.decorators import csp_update
|
||||
from lasuite.malware_detection import malware_detection
|
||||
from lasuite.oidc_login.decorators import refresh_oidc_access_token
|
||||
from lasuite.tools.email import get_domain_from_email
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
from rest_framework import filters, status, viewsets
|
||||
@@ -676,17 +674,8 @@ class DocumentViewSet(
|
||||
|
||||
return drf.response.Response(serializer.data)
|
||||
|
||||
@transaction.atomic
|
||||
def perform_create(self, serializer):
|
||||
"""Set the current user as creator and owner of the newly created object."""
|
||||
|
||||
# locks the table to ensure safe concurrent access
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001
|
||||
"IN SHARE ROW EXCLUSIVE MODE;"
|
||||
)
|
||||
|
||||
# Remove file from validated_data as it's not a model field
|
||||
# Process it if present
|
||||
uploaded_file = serializer.validated_data.pop("file", None)
|
||||
@@ -704,15 +693,25 @@ class DocumentViewSet(
|
||||
)
|
||||
serializer.validated_data["content"] = converted_content
|
||||
serializer.validated_data["title"] = uploaded_file.name
|
||||
logger.info("conversion ended successfully")
|
||||
except ConversionError as err:
|
||||
logger.error("could not convert file content with error: %s", err)
|
||||
raise drf.exceptions.ValidationError(
|
||||
{"file": ["Could not convert file content"]}
|
||||
) from err
|
||||
|
||||
obj = models.Document.add_root(
|
||||
creator=self.request.user,
|
||||
**serializer.validated_data,
|
||||
)
|
||||
with transaction.atomic():
|
||||
# locks the table to ensure safe concurrent access
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
f'LOCK TABLE "{models.Document._meta.db_table}" ' # noqa: SLF001
|
||||
"IN SHARE ROW EXCLUSIVE MODE;"
|
||||
)
|
||||
|
||||
obj = models.Document.add_root(
|
||||
creator=self.request.user,
|
||||
**serializer.validated_data,
|
||||
)
|
||||
serializer.instance = obj
|
||||
models.DocumentAccess.objects.create(
|
||||
document=obj,
|
||||
@@ -829,6 +828,7 @@ class DocumentViewSet(
|
||||
|
||||
queryset = self.queryset.filter(path_list)
|
||||
queryset = queryset.filter(id__in=favorite_documents_ids)
|
||||
queryset = queryset.filter(ancestors_deleted_at__isnull=True)
|
||||
queryset = queryset.annotate_user_roles(user)
|
||||
queryset = queryset.annotate(
|
||||
is_favorite=db.Value(True, output_field=db.BooleanField())
|
||||
@@ -1415,7 +1415,7 @@ class DocumentViewSet(
|
||||
return duplicated_document
|
||||
|
||||
@drf.decorators.action(detail=False, methods=["get"], url_path="search")
|
||||
@method_decorator(refresh_oidc_access_token)
|
||||
@utils.conditional_refresh_oidc_token
|
||||
def search(self, request, *args, **kwargs):
|
||||
"""
|
||||
Returns an ordered list of documents best matching the search query parameter 'q'.
|
||||
@@ -1426,7 +1426,6 @@ class DocumentViewSet(
|
||||
params = serializers.SearchDocumentSerializer(data=request.query_params)
|
||||
params.is_valid(raise_exception=True)
|
||||
search_type = self._get_search_type()
|
||||
|
||||
if search_type == SearchType.TITLE:
|
||||
return self._title_search(request, params.validated_data, *args, **kwargs)
|
||||
|
||||
|
||||
+10
-11
@@ -267,6 +267,16 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
|
||||
if settings.USER_ONBOARDING_SANDBOX_DOCUMENT:
|
||||
# transaction.atomic is used in a context manager to avoid a transaction if
|
||||
# the settings USER_ONBOARDING_SANDBOX_DOCUMENT is unused
|
||||
sandbox_id = settings.USER_ONBOARDING_SANDBOX_DOCUMENT
|
||||
try:
|
||||
template_document = Document.objects.get(id=sandbox_id)
|
||||
except Document.DoesNotExist:
|
||||
logger.warning(
|
||||
"Onboarding sandbox document with id %s does not exist. Skipping.",
|
||||
sandbox_id,
|
||||
)
|
||||
return
|
||||
|
||||
with transaction.atomic():
|
||||
# locks the table to ensure safe concurrent access
|
||||
with connection.cursor() as cursor:
|
||||
@@ -274,17 +284,6 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
|
||||
f'LOCK TABLE "{Document._meta.db_table}" ' # noqa: SLF001
|
||||
"IN SHARE ROW EXCLUSIVE MODE;"
|
||||
)
|
||||
|
||||
sandbox_id = settings.USER_ONBOARDING_SANDBOX_DOCUMENT
|
||||
try:
|
||||
template_document = Document.objects.get(id=sandbox_id)
|
||||
except Document.DoesNotExist:
|
||||
logger.warning(
|
||||
"Onboarding sandbox document with id %s does not exist. Skipping.",
|
||||
sandbox_id,
|
||||
)
|
||||
return
|
||||
|
||||
sandbox_document = Document.add_root(
|
||||
title=template_document.title,
|
||||
content=template_document.content,
|
||||
|
||||
@@ -45,6 +45,8 @@ class Converter:
|
||||
def convert(self, data, content_type, accept):
|
||||
"""Convert input into other formats using external microservices."""
|
||||
|
||||
logger.info("converting content from %s to %s", content_type, accept)
|
||||
|
||||
if content_type == mime_types.DOCX and accept == mime_types.YJS:
|
||||
blocknote_data = self.docspec.convert(
|
||||
data, mime_types.DOCX, mime_types.BLOCKNOTE
|
||||
|
||||
@@ -114,3 +114,29 @@ def test_api_document_favorite_list_with_favorite_children():
|
||||
assert content[0]["id"] == str(children[0].id)
|
||||
assert content[1]["id"] == str(children[1].id)
|
||||
assert content[2]["id"] == str(access.document.id)
|
||||
|
||||
|
||||
def test_api_document_favorite_list_with_deleted_child():
|
||||
"""
|
||||
Authenticated users should not see deleted documents in their favorite list.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
root = factories.DocumentFactory(creator=user, users=[user], favorited_by=[user])
|
||||
child1, child2 = factories.DocumentFactory.create_batch(
|
||||
2, parent=root, favorited_by=[user]
|
||||
)
|
||||
|
||||
child1.delete()
|
||||
|
||||
response = client.get("/api/v1.0/documents/favorite_list/")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json()["count"] == 2
|
||||
|
||||
content = response.json()["results"]
|
||||
|
||||
assert content[0]["id"] == str(root.id)
|
||||
assert content[1]["id"] == str(child2.id)
|
||||
|
||||
@@ -70,17 +70,20 @@ def test_api_documents_search_anonymous(search_query, indexer_settings):
|
||||
|
||||
|
||||
@mock.patch("core.api.viewsets.DocumentViewSet.list")
|
||||
def test_api_documents_search_fall_back_on_search_list(mock_list, indexer_settings):
|
||||
def test_api_documents_search_fall_back_on_search_list(mock_list, settings):
|
||||
"""
|
||||
When indexer is not configured and no path is provided,
|
||||
should fall back on list method
|
||||
"""
|
||||
indexer_settings.SEARCH_URL = None
|
||||
assert get_document_indexer() is None
|
||||
assert settings.OIDC_STORE_REFRESH_TOKEN is False
|
||||
assert settings.OIDC_STORE_ACCESS_TOKEN is False
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
client.force_login(
|
||||
user, backend="core.authentication.backends.OIDCAuthenticationBackend"
|
||||
)
|
||||
|
||||
mocked_response = {
|
||||
"count": 0,
|
||||
@@ -93,6 +96,8 @@ def test_api_documents_search_fall_back_on_search_list(mock_list, indexer_settin
|
||||
q = "alpha"
|
||||
response = client.get("/api/v1.0/documents/search/", data={"q": q})
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
assert mock_list.call_count == 1
|
||||
assert mock_list.call_args[0][0].GET.get("q") == q
|
||||
assert response.json() == mocked_response
|
||||
@@ -100,18 +105,21 @@ def test_api_documents_search_fall_back_on_search_list(mock_list, indexer_settin
|
||||
|
||||
@mock.patch("core.api.viewsets.DocumentViewSet._list_descendants")
|
||||
def test_api_documents_search_fallback_on_search_list_sub_docs(
|
||||
mock_list_descendants, indexer_settings
|
||||
mock_list_descendants, settings
|
||||
):
|
||||
"""
|
||||
When indexer is not configured and path parameter is provided,
|
||||
should call _list_descendants() method
|
||||
"""
|
||||
indexer_settings.SEARCH_URL = "http://find/api/v1.0/search"
|
||||
assert get_document_indexer() is not None
|
||||
assert get_document_indexer() is None
|
||||
assert settings.OIDC_STORE_REFRESH_TOKEN is False
|
||||
assert settings.OIDC_STORE_ACCESS_TOKEN is False
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
client.force_login(
|
||||
user, backend="core.authentication.backends.OIDCAuthenticationBackend"
|
||||
)
|
||||
|
||||
parent = factories.DocumentFactory(title="parent", users=[user])
|
||||
|
||||
@@ -128,9 +136,9 @@ def test_api_documents_search_fallback_on_search_list_sub_docs(
|
||||
"/api/v1.0/documents/search/", data={"q": q, "path": parent.path}
|
||||
)
|
||||
|
||||
assert mock_list_descendants.call_count == 1
|
||||
assert mock_list_descendants.call_args[0][0].GET.get("q") == q
|
||||
assert mock_list_descendants.call_args[0][0].GET.get("path") == parent.path
|
||||
mock_list_descendants.assert_called_with(
|
||||
mock.ANY, {"q": "alpha", "path": parent.path}
|
||||
)
|
||||
assert response.json() == mocked_response
|
||||
|
||||
|
||||
@@ -152,7 +160,9 @@ def test_api_documents_search_indexer_crashes(mock_title_search, indexer_setting
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
client.force_login(
|
||||
user, backend="core.authentication.backends.OIDCAuthenticationBackend"
|
||||
)
|
||||
|
||||
mocked_response = {
|
||||
"count": 0,
|
||||
@@ -185,7 +195,9 @@ def test_api_documents_search_invalid_params(indexer_settings):
|
||||
|
||||
user = factories.UserFactory()
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
client.force_login(
|
||||
user, backend="core.authentication.backends.OIDCAuthenticationBackend"
|
||||
)
|
||||
|
||||
response = client.get("/api/v1.0/documents/search/")
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""
|
||||
Tests for Documents API endpoint in impress's core app: update
|
||||
"""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import random
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.cache import cache
|
||||
@@ -17,6 +19,25 @@ from core.tests.conftest import TEAM, USER, VIA
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
# A valid Yjs document derived from YDOC_HELLO_WORLD_BASE64 with "Hello" replaced by "World",
|
||||
# used in PATCH tests to guarantee a real content change distinct from what DocumentFactory
|
||||
# produces.
|
||||
YDOC_UPDATED_CONTENT_BASE64 = (
|
||||
"AR717vLVDgAHAQ5kb2N1bWVudC1zdG9yZQMKYmxvY2tHcm91cAcA9e7y1Q4AAw5ibG9ja0NvbnRh"
|
||||
"aW5lcgcA9e7y1Q4BAwdoZWFkaW5nBwD17vLVDgIGBgD17vLVDgMGaXRhbGljAnt9hPXu8tUOBAVX"
|
||||
"b3JsZIb17vLVDgkGaXRhbGljBG51bGwoAPXu8tUOAg10ZXh0QWxpZ25tZW50AXcEbGVmdCgA9e7y"
|
||||
"1Q4CBWxldmVsAX0BKAD17vLVDgECaWQBdyQwNGQ2MjM0MS04MzI2LTQyMzYtYTA4My00ODdlMjZm"
|
||||
"YWQyMzAoAPXu8tUOAQl0ZXh0Q29sb3IBdwdkZWZhdWx0KAD17vLVDgEPYmFja2dyb3VuZENvbG9y"
|
||||
"AXcHZGVmYXVsdIf17vLVDgEDDmJsb2NrQ29udGFpbmVyBwD17vLVDhADDmJ1bGxldExpc3RJdGVt"
|
||||
"BwD17vLVDhEGBAD17vLVDhIBd4b17vLVDhMEYm9sZAJ7fYT17vLVDhQCb3KG9e7y1Q4WBGJvbGQE"
|
||||
"bnVsbIT17vLVDhcCbGQoAPXu8tUOEQ10ZXh0QWxpZ25tZW50AXcEbGVmdCgA9e7y1Q4QAmlkAXck"
|
||||
"ZDM1MWUwNjgtM2U1NS00MjI2LThlYTUtYWJiMjYzMTk4ZTJhKAD17vLVDhAJdGV4dENvbG9yAXcH"
|
||||
"ZGVmYXVsdCgA9e7y1Q4QD2JhY2tncm91bmRDb2xvcgF3B2RlZmF1bHSH9e7y1Q4QAw5ibG9ja0Nv"
|
||||
"bnRhaW5lcgcA9e7y1Q4eAwlwYXJhZ3JhcGgoAPXu8tUOHw10ZXh0QWxpZ25tZW50AXcEbGVmdCgA"
|
||||
"9e7y1Q4eAmlkAXckODk3MDBjMDctZTBlMS00ZmUwLWFjYTItODQ5MzIwOWE3ZTQyKAD17vLVDh4J"
|
||||
"dGV4dENvbG9yAXcHZGVmYXVsdCgA9e7y1Q4eD2JhY2tncm91bmRDb2xvcgF3B2RlZmF1bHQA"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via_parent", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
@@ -330,6 +351,7 @@ def test_api_documents_update_authenticated_no_websocket(settings):
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
@@ -338,6 +360,8 @@ def test_api_documents_update_authenticated_no_websocket(settings):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
document.refresh_from_db()
|
||||
assert document.path == old_path
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == session_key
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
@@ -446,6 +470,7 @@ def test_api_documents_update_user_connected_to_websocket(settings):
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": True})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
@@ -453,6 +478,9 @@ def test_api_documents_update_user_connected_to_websocket(settings):
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
document.refresh_from_db()
|
||||
assert document.path == old_path
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
@@ -486,6 +514,7 @@ def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websoc
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
@@ -494,6 +523,8 @@ def test_api_documents_update_websocket_server_unreachable_fallback_to_no_websoc
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
document.refresh_from_db()
|
||||
assert document.path == old_path
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == session_key
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
@@ -605,6 +636,7 @@ def test_api_documents_update_force_websocket_param_to_true(settings):
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
@@ -613,6 +645,8 @@ def test_api_documents_update_force_websocket_param_to_true(settings):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
document.refresh_from_db()
|
||||
assert document.path == old_path
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 0
|
||||
|
||||
@@ -643,6 +677,7 @@ def test_api_documents_update_feature_flag_disabled(settings):
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
@@ -651,6 +686,8 @@ def test_api_documents_update_feature_flag_disabled(settings):
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
document.refresh_from_db()
|
||||
assert document.path == old_path
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 0
|
||||
|
||||
@@ -716,3 +753,724 @@ def test_api_documents_update_invalid_content():
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"content": ["Invalid base64 content."]}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PATCH tests
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via_parent", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
"reach, role",
|
||||
[
|
||||
("restricted", "reader"),
|
||||
("restricted", "editor"),
|
||||
("authenticated", "reader"),
|
||||
("authenticated", "editor"),
|
||||
("public", "reader"),
|
||||
],
|
||||
)
|
||||
def test_api_documents_patch_anonymous_forbidden(reach, role, via_parent):
|
||||
"""
|
||||
Anonymous users should not be allowed to patch a document when link
|
||||
configuration does not allow it.
|
||||
"""
|
||||
if via_parent:
|
||||
grand_parent = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
document = factories.DocumentFactory(parent=parent, link_reach="restricted")
|
||||
else:
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
response = APIClient().patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 401
|
||||
assert response.json() == {
|
||||
"detail": "Authentication credentials were not provided."
|
||||
}
|
||||
|
||||
document.refresh_from_db()
|
||||
assert serializers.DocumentSerializer(instance=document).data == old_document_values
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via_parent", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
"reach,role",
|
||||
[
|
||||
("public", "reader"),
|
||||
("authenticated", "reader"),
|
||||
("restricted", "reader"),
|
||||
("restricted", "editor"),
|
||||
],
|
||||
)
|
||||
def test_api_documents_patch_authenticated_unrelated_forbidden(reach, role, via_parent):
|
||||
"""
|
||||
Authenticated users should not be allowed to patch a document to which
|
||||
they are not related if the link configuration does not allow it.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
if via_parent:
|
||||
grand_parent = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
document = factories.DocumentFactory(parent=parent, link_reach="restricted")
|
||||
else:
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
document.refresh_from_db()
|
||||
assert serializers.DocumentSerializer(instance=document).data == old_document_values
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via_parent", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
"is_authenticated,reach,role",
|
||||
[
|
||||
(False, "public", "editor"),
|
||||
(True, "public", "editor"),
|
||||
(True, "authenticated", "editor"),
|
||||
],
|
||||
)
|
||||
def test_api_documents_patch_anonymous_or_authenticated_unrelated(
|
||||
is_authenticated, reach, role, via_parent
|
||||
):
|
||||
"""
|
||||
Anonymous and authenticated users should be able to patch a document to which
|
||||
they are not related if the link configuration allows it.
|
||||
"""
|
||||
client = APIClient()
|
||||
|
||||
if is_authenticated:
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client.force_login(user)
|
||||
|
||||
if via_parent:
|
||||
grand_parent = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
document = factories.DocumentFactory(parent=parent, link_reach="restricted")
|
||||
else:
|
||||
document = factories.DocumentFactory(link_reach=reach, link_role=role)
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
old_path = document.path
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content, "websocket": True},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not wirk because the content is in cache.
|
||||
# Force reloading it by fetching the document in the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
document_values = serializers.DocumentSerializer(instance=document).data
|
||||
for key in [
|
||||
"id",
|
||||
"title",
|
||||
"link_reach",
|
||||
"link_role",
|
||||
"creator",
|
||||
"depth",
|
||||
"numchild",
|
||||
"path",
|
||||
]:
|
||||
assert document_values[key] == old_document_values[key]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via_parent", [True, False])
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_documents_patch_authenticated_reader(via, via_parent, mock_user_teams):
|
||||
"""Users who are reader of a document should not be allowed to patch it."""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
if via_parent:
|
||||
grand_parent = factories.DocumentFactory(link_reach="restricted")
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
document = factories.DocumentFactory(parent=parent, link_reach="restricted")
|
||||
access_document = grand_parent
|
||||
else:
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
access_document = document
|
||||
|
||||
if via == USER:
|
||||
factories.UserDocumentAccessFactory(
|
||||
document=access_document, user=user, role="reader"
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamDocumentAccessFactory(
|
||||
document=access_document, team="lasuite", role="reader"
|
||||
)
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
document.refresh_from_db()
|
||||
assert serializers.DocumentSerializer(instance=document).data == old_document_values
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via_parent", [True, False])
|
||||
@pytest.mark.parametrize("role", ["editor", "administrator", "owner"])
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_documents_patch_authenticated_editor_administrator_or_owner(
|
||||
via, role, via_parent, mock_user_teams
|
||||
):
|
||||
"""A user who is editor, administrator or owner of a document should be allowed to patch it."""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
if via_parent:
|
||||
grand_parent = factories.DocumentFactory(link_reach="restricted")
|
||||
parent = factories.DocumentFactory(parent=grand_parent, link_reach="restricted")
|
||||
document = factories.DocumentFactory(parent=parent, link_reach="restricted")
|
||||
access_document = grand_parent
|
||||
else:
|
||||
document = factories.DocumentFactory(link_reach="restricted")
|
||||
access_document = document
|
||||
|
||||
if via == USER:
|
||||
factories.UserDocumentAccessFactory(
|
||||
document=access_document, user=user, role=role
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamDocumentAccessFactory(
|
||||
document=access_document, team="lasuite", role=role
|
||||
)
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
old_path = document.path
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content, "websocket": True},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not wirk because the content is in cache.
|
||||
# Force reloading it by fetching the document in the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
document_values = serializers.DocumentSerializer(instance=document).data
|
||||
for key in [
|
||||
"id",
|
||||
"title",
|
||||
"link_reach",
|
||||
"link_role",
|
||||
"creator",
|
||||
"depth",
|
||||
"numchild",
|
||||
"path",
|
||||
"nb_accesses_ancestors",
|
||||
"nb_accesses_direct",
|
||||
]:
|
||||
assert document_values[key] == old_document_values[key]
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_authenticated_no_websocket(settings):
|
||||
"""
|
||||
When a user patches the document, not connected to the websocket and is the first to update,
|
||||
the document should be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not work because the content is cached.
|
||||
# Force reloading it by fetching the document from the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == session_key
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_authenticated_no_websocket_user_already_editing(settings):
|
||||
"""
|
||||
When a user patches the document, not connected to the websocket and is not the first to
|
||||
update, the document should not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 0, "exists": False})
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "You are not allowed to edit this document."}
|
||||
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_no_websocket_other_user_connected_to_websocket(settings):
|
||||
"""
|
||||
When a user patches the document, not connected to the websocket and another user is connected
|
||||
to the websocket, the document should not be updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": False})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "You are not allowed to edit this document."}
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_user_connected_to_websocket(settings):
|
||||
"""
|
||||
When a user patches the document while connected to the websocket, the document should be
|
||||
updated.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": True})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not wirk because the content is in cache.
|
||||
# Force reloading it by fetching the document in the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_websocket_server_unreachable_fallback_to_no_websocket(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server is unreachable, the patch should be applied like if the user was
|
||||
not connected to the websocket.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not work because the content is cached.
|
||||
# Force reloading it by fetching the document from the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == session_key
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_websocket_server_unreachable_fallback_to_no_websocket_other_users(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the websocket server is unreachable, the behavior falls back to no-websocket.
|
||||
If another user is already editing, the patch must be denied.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_websocket_server_room_not_found_fallback_to_no_websocket_other_users(
|
||||
settings,
|
||||
):
|
||||
"""
|
||||
When the WebSocket server does not have the room created, the logic should fallback to
|
||||
no-WebSocket. If another user is already editing, the patch must be denied.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=404)
|
||||
|
||||
cache.set(f"docs:no-websocket:{document.id}", "other_session_key")
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") == "other_session_key"
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_force_websocket_param_to_true(settings):
|
||||
"""
|
||||
When the websocket parameter is set to true, the patch should be applied without any check.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content, "websocket": True},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not work because the content is cached.
|
||||
# Force reloading it by fetching the document from the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 0
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_feature_flag_disabled(settings):
|
||||
"""
|
||||
When the feature flag is disabled, the patch should be applied without any check.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "editor")])
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = False
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, status=500)
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
old_path = document.path
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Using document.refresh_from_db does not work because the content is cached.
|
||||
# Force reloading it by fetching the document from the database.
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
assert document.path == old_path
|
||||
assert document.content == new_content
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("via", VIA)
|
||||
def test_api_documents_patch_administrator_or_owner_of_another(via, mock_user_teams):
|
||||
"""
|
||||
Being administrator or owner of a document should not grant authorization to patch
|
||||
another document.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory()
|
||||
if via == USER:
|
||||
factories.UserDocumentAccessFactory(
|
||||
document=document, user=user, role=random.choice(["administrator", "owner"])
|
||||
)
|
||||
elif via == TEAM:
|
||||
mock_user_teams.return_value = ["lasuite", "unknown"]
|
||||
factories.TeamDocumentAccessFactory(
|
||||
document=document,
|
||||
team="lasuite",
|
||||
role=random.choice(["administrator", "owner"]),
|
||||
)
|
||||
|
||||
other_document = factories.DocumentFactory(title="Old title", link_role="reader")
|
||||
old_document_values = serializers.DocumentSerializer(instance=other_document).data
|
||||
new_content = YDOC_UPDATED_CONTENT_BASE64
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{other_document.id!s}/",
|
||||
{"content": new_content},
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
other_document.refresh_from_db()
|
||||
assert (
|
||||
serializers.DocumentSerializer(instance=other_document).data
|
||||
== old_document_values
|
||||
)
|
||||
|
||||
|
||||
def test_api_documents_patch_invalid_content():
|
||||
"""
|
||||
Patching a document with a non base64 encoded content should raise a validation error.
|
||||
"""
|
||||
user = factories.UserFactory(with_owned_document=True)
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
document = factories.DocumentFactory(users=[[user, "owner"]])
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
{"content": "invalid content"},
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == 400
|
||||
assert response.json() == {"content": ["Invalid base64 content."]}
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_api_documents_patch_empty_body(settings):
|
||||
"""
|
||||
Test when data is empty the document should not be updated.
|
||||
The `updated_at` property should not change asserting that no update in the database is made.
|
||||
"""
|
||||
user = factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
session_key = client.session.session_key
|
||||
|
||||
document = factories.DocumentFactory(users=[(user, "owner")], creator=user)
|
||||
document_updated_at = document.updated_at
|
||||
|
||||
settings.COLLABORATION_API_URL = "http://example.com/"
|
||||
settings.COLLABORATION_SERVER_SECRET = "secret-token"
|
||||
settings.COLLABORATION_WS_NOT_CONNECTED_READY_ONLY = True
|
||||
endpoint_url = (
|
||||
f"{settings.COLLABORATION_API_URL}get-connections/"
|
||||
f"?room={document.id}&sessionKey={session_key}"
|
||||
)
|
||||
ws_resp = responses.get(endpoint_url, json={"count": 3, "exists": True})
|
||||
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
|
||||
old_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
|
||||
with patch("core.models.Document.save") as mock_document_save:
|
||||
response = client.patch(
|
||||
f"/api/v1.0/documents/{document.id!s}/",
|
||||
content_type="application/json",
|
||||
)
|
||||
mock_document_save.assert_not_called()
|
||||
assert response.status_code == 200
|
||||
|
||||
document = models.Document.objects.get(id=document.id)
|
||||
new_document_values = serializers.DocumentSerializer(instance=document).data
|
||||
assert new_document_values == old_document_values
|
||||
assert document_updated_at == document.updated_at
|
||||
assert cache.get(f"docs:no-websocket:{document.id}") is None
|
||||
assert ws_resp.call_count == 1
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""module testing the conditional_refresh_oidc_token utils."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from core.api import utils
|
||||
|
||||
|
||||
def test_refresh_oidc_access_token_storing_refresh_token_disabled(settings):
|
||||
"""The method_decorator must not be called when OIDC_STORE_REFRESH_TOKEN is False."""
|
||||
|
||||
settings.OIDC_STORE_REFRESH_TOKEN = False
|
||||
|
||||
callback = mock.MagicMock()
|
||||
|
||||
with mock.patch.object(utils, "method_decorator") as mock_method_decorator:
|
||||
result = utils.conditional_refresh_oidc_token(callback)
|
||||
|
||||
mock_method_decorator.assert_not_called()
|
||||
assert result == callback
|
||||
|
||||
|
||||
def test_refresh_oidc_access_token_storing_refresh_token_enabled(settings):
|
||||
"""The method_decorator must not be called when OIDC_STORE_REFRESH_TOKEN is False."""
|
||||
|
||||
settings.OIDC_STORE_REFRESH_TOKEN = True
|
||||
|
||||
callback = mock.MagicMock()
|
||||
|
||||
with mock.patch.object(utils, "method_decorator") as mock_method_decorator:
|
||||
utils.conditional_refresh_oidc_token(callback)
|
||||
|
||||
mock_method_decorator.assert_called_with(utils.refresh_oidc_access_token)
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Breton\n"
|
||||
"Language: br_FR\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Gwezennadur"
|
||||
msgid "Title"
|
||||
msgstr "Titl"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Me eo an aozer"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Kuzhet"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Sinedoù"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ur restr nevez a zo bet krouet ganeoc'h!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "C'hwi zo bet disklaeriet perc'henn ur restr nevez:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "Ar vaezienn-mañ a zo rekis."
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "eilenn {title}"
|
||||
@@ -247,98 +251,98 @@ msgstr "implijer"
|
||||
msgid "users"
|
||||
msgstr "implijerien"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "titl"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "bomm"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Restr"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Restroù"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Restr hep titl"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Digeriñ"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} en deus rannet ur restr ganeoc'h!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, 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:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Roud liamm ar restr/an implijer"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Roudoù liamm ar restr/an implijer"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
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:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Restr muiañ-karet"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Restroù muiañ-karet"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
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:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Liamm restr/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Liammoù restr/implijer"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "An implijer-mañ a zo dija er restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ar skipailh-mañ a zo dija en restr-mañ."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
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:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Goulenn tizhout ar restr"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Goulennoù tizhout ar restr"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
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:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, 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:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, 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:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, 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:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "postel"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Pedadenn d'ur restr"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Pedadennoù d'ur restr"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ar postel-mañ a zo liammet ouzh un implijer enskrivet."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -28,11 +28,11 @@ msgstr "Berechtigungen"
|
||||
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Wichtige Daten"
|
||||
msgstr "Wichtige Termine"
|
||||
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr ""
|
||||
msgstr "Import-Job erstellt und in der Warteschlange."
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
@@ -46,36 +46,40 @@ msgstr "Baumstruktur"
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr "Suchen"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Ersteller bin ich"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Maskiert"
|
||||
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Favorit"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ein neues Dokument wurde in Ihrem Namen erstellt!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sie sind Besitzer eines neuen Dokuments:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
msgstr "Dies ist ein Pflichtfeld."
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
msgstr "Der Zugriff auf den Link '%(link_reach)s' ist aufgrund der Konfiguration übergeordneter Dokumente nicht erlaubt."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "Kopie von {title}"
|
||||
@@ -92,7 +96,7 @@ msgstr "Lesen"
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:44 core/choices.py:36
|
||||
#: core/choices.py:44
|
||||
msgid "Commenter"
|
||||
msgstr ""
|
||||
msgstr "Kommentieren"
|
||||
|
||||
#: build/lib/core/choices.py:37 build/lib/core/choices.py:45 core/choices.py:37
|
||||
#: core/choices.py:45
|
||||
@@ -173,11 +177,11 @@ msgstr "Wir konnten keinen Benutzer mit diesem Abo finden, aber die E-Mail-Adres
|
||||
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "unter"
|
||||
msgstr "sub"
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr ""
|
||||
msgstr "Pflichtfeld. 255 Zeichen oder weniger. Buchstaben (nur ASCII), Ziffern und die Zeichen @/-/_/."
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
@@ -233,11 +237,11 @@ msgstr "Ob dieser Benutzer als aktiviert behandelt werden soll. Deaktivieren Sie
|
||||
|
||||
#: build/lib/core/models.py:197 core/models.py:197
|
||||
msgid "first connection status"
|
||||
msgstr ""
|
||||
msgstr "Status der ersten Verbindung"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
msgid "Whether the user has completed the first connection process."
|
||||
msgstr ""
|
||||
msgstr "Gibt an, ob der Benutzer die Prozedur der ersten Verbindung abgeschlossen hat."
|
||||
|
||||
#: build/lib/core/models.py:209 core/models.py:209
|
||||
msgid "user"
|
||||
@@ -247,98 +251,98 @@ msgstr "Benutzer"
|
||||
msgid "users"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
msgstr "Aktive E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
msgstr "Zu deaktivierende E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "Ausstehend"
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Bereit"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
msgstr "Fertig"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
msgstr "Fehler"
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
msgstr "Klicken Sie hier"
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
msgstr "Bestätigen Sie"
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
msgstr "Ihre Konten wurden zusammengelegt"
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
msgstr "Klicken Sie hier um zu sehen"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
msgstr "Meine Dokumente einsehen"
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
msgstr "CSV-Datei"
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
msgstr "Wird ausgeführt"
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,177 +351,177 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
msgstr "Neue Anfrage erstellen"
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "Auszug"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Dokumente"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Unbenanntes Dokument"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Öffnen"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} hat ein Dokument mit Ihnen geteilt!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, 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:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Dokument/Benutzer Linkverfolgung"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
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:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Dokumentenfavorit"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Dokumentfavoriten"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
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:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Dokument/Benutzerbeziehung"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Dokument/Benutzerbeziehungen"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Dieser Benutzer befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dieses Team befindet sich bereits in diesem Dokument."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
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:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
msgstr "Dokument um Zugriff bitten"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
msgstr "Dokumentenabfragen"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
msgstr "Dieser Benutzer hat bereits um Zugang zu diesem Dokument gebeten."
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
msgstr "{name} möchte Zugriff auf ein Dokument erhalten!"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
msgstr "{name} möchte auf das folgende Dokument zugreifen:"
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
msgstr "{name} bittet um Zugang zum Dokument: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "Thread"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
msgstr "Threads"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
msgstr "Gast"
|
||||
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
msgstr "Kommentare"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
msgstr "Reaktion"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
msgstr "Reaktionen"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "E-Mail-Adresse"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Einladung zum Dokument"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Dokumenteinladungen"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Diese E-Mail ist bereits einem registrierten Benutzer zugeordnet."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
msgstr "Docs AI"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Greek\n"
|
||||
"Language: el_GR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Crowdin-Project: lasuite-docs\n"
|
||||
"X-Crowdin-Project-ID: 754523\n"
|
||||
"X-Crowdin-Language: el\n"
|
||||
"X-Crowdin-File: backend-impress.pot\n"
|
||||
"X-Crowdin-File-ID: 18\n"
|
||||
|
||||
#: build/lib/core/admin.py:30 core/admin.py:30
|
||||
msgid "Personal info"
|
||||
msgstr "Προσωπικές πληροφορίες"
|
||||
|
||||
#: build/lib/core/admin.py:43 build/lib/core/admin.py:161 core/admin.py:43
|
||||
#: core/admin.py:161
|
||||
msgid "Permissions"
|
||||
msgstr "Δικαιώματα"
|
||||
|
||||
#: build/lib/core/admin.py:55 core/admin.py:55
|
||||
msgid "Important dates"
|
||||
msgstr "Σημαντικές ημερομηνίες"
|
||||
|
||||
#: build/lib/core/admin.py:112 core/admin.py:112
|
||||
msgid "Import job created and queued."
|
||||
msgstr "Η εργασία εισαγωγής δημιουργήθηκε και μπήκε στην ουρά."
|
||||
|
||||
#: build/lib/core/admin.py:116 core/admin.py:116
|
||||
msgid "Process selected user reconciliations"
|
||||
msgstr "Επεξεργασία επιλεγμένων συμφωνιών χρηστών"
|
||||
|
||||
#: build/lib/core/admin.py:171 core/admin.py:171
|
||||
msgid "Tree structure"
|
||||
msgstr "Δομή δέντρου"
|
||||
|
||||
#: build/lib/core/api/filters.py:48 core/api/filters.py:48
|
||||
msgid "Title"
|
||||
msgstr "Τίτλος"
|
||||
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr "Αναζήτηση"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Δημιουργός είμαι εγώ"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Με κάλυψη"
|
||||
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Αγαπημένο"
|
||||
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ένα νέο έγγραφο δημιουργήθηκε εκ μέρους σας!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Σας παραχωρήθηκε η ιδιοκτησία ενός νέου εγγράφου:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "Αυτό το πεδίο είναι υποχρεωτικό."
|
||||
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Η εμβέλεια συνδέσμου '%(link_reach)s' δεν επιτρέπεται βάσει της διαμόρφωσης του γονικού εγγράφου."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "αντίγραφο του {title}"
|
||||
|
||||
#: build/lib/core/apps.py:12 core/apps.py:12
|
||||
msgid "Impress core application"
|
||||
msgstr "Κεντρική εφαρμογή Impress"
|
||||
|
||||
#: build/lib/core/choices.py:35 build/lib/core/choices.py:43 core/choices.py:35
|
||||
#: core/choices.py:43
|
||||
msgid "Reader"
|
||||
msgstr "Αναγνώστης"
|
||||
|
||||
#: build/lib/core/choices.py:36 build/lib/core/choices.py:44 core/choices.py:36
|
||||
#: core/choices.py:44
|
||||
msgid "Commenter"
|
||||
msgstr "Σχολιαστής"
|
||||
|
||||
#: build/lib/core/choices.py:37 build/lib/core/choices.py:45 core/choices.py:37
|
||||
#: core/choices.py:45
|
||||
msgid "Editor"
|
||||
msgstr "Συντάκτης"
|
||||
|
||||
#: build/lib/core/choices.py:46 core/choices.py:46
|
||||
msgid "Administrator"
|
||||
msgstr "Διαχειριστής"
|
||||
|
||||
#: build/lib/core/choices.py:47 core/choices.py:47
|
||||
msgid "Owner"
|
||||
msgstr "Ιδιοκτήτης"
|
||||
|
||||
#: build/lib/core/choices.py:58 core/choices.py:58
|
||||
msgid "Restricted"
|
||||
msgstr "Περιορισμένο"
|
||||
|
||||
#: build/lib/core/choices.py:62 core/choices.py:62
|
||||
msgid "Authenticated"
|
||||
msgstr "Πιστοποιημένο"
|
||||
|
||||
#: build/lib/core/choices.py:64 core/choices.py:64
|
||||
msgid "Public"
|
||||
msgstr "Δημόσιο"
|
||||
|
||||
#: build/lib/core/enums.py:36 core/enums.py:36
|
||||
msgid "First child"
|
||||
msgstr "Πρώτο θυγατρικό"
|
||||
|
||||
#: build/lib/core/enums.py:37 core/enums.py:37
|
||||
msgid "Last child"
|
||||
msgstr "Τελευταίο θυγατρικό"
|
||||
|
||||
#: build/lib/core/enums.py:38 core/enums.py:38
|
||||
msgid "First sibling"
|
||||
msgstr "Πρώτο αδελφό"
|
||||
|
||||
#: build/lib/core/enums.py:39 core/enums.py:39
|
||||
msgid "Last sibling"
|
||||
msgstr "Τελευταίο αδελφό"
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
msgid "Left"
|
||||
msgstr "Αριστερά"
|
||||
|
||||
#: build/lib/core/enums.py:41 core/enums.py:41
|
||||
msgid "Right"
|
||||
msgstr "Δεξιά"
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr "αναγνωριστικό"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
msgstr "πρωτεύον κλειδί για την εγγραφή ως UUID"
|
||||
|
||||
#: build/lib/core/models.py:87 core/models.py:87
|
||||
msgid "created on"
|
||||
msgstr "δημιουργήθηκε στις"
|
||||
|
||||
#: build/lib/core/models.py:88 core/models.py:88
|
||||
msgid "date and time at which a record was created"
|
||||
msgstr "ημερομηνία και ώρα δημιουργίας μιας εγγραφής"
|
||||
|
||||
#: build/lib/core/models.py:93 core/models.py:93
|
||||
msgid "updated on"
|
||||
msgstr "ενημερώθηκε στις"
|
||||
|
||||
#: build/lib/core/models.py:94 core/models.py:94
|
||||
msgid "date and time at which a record was last updated"
|
||||
msgstr "ημερομηνία και ώρα τελευταίας ενημέρωσης μιας εγγραφής"
|
||||
|
||||
#: build/lib/core/models.py:130 core/models.py:130
|
||||
msgid "We couldn't find a user with this sub but the email is already associated with a registered user."
|
||||
msgstr "Δεν μπορέσαμε να βρούμε χρήστη με αυτό το sub, αλλά το email σχετίζεται ήδη με έναν εγγεγραμμένο χρήστη."
|
||||
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr "sub (αναγνωριστικό υποκειμένου)"
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
msgstr "Υποχρεωτικό. 255 χαρακτήρες ή λιγότεροι. Μόνο χαρακτήρες ASCII."
|
||||
|
||||
#: build/lib/core/models.py:150 core/models.py:150
|
||||
msgid "full name"
|
||||
msgstr "πλήρες όνομα"
|
||||
|
||||
#: build/lib/core/models.py:152 core/models.py:152
|
||||
msgid "short name"
|
||||
msgstr "σύντομο όνομα"
|
||||
|
||||
#: build/lib/core/models.py:155 core/models.py:155
|
||||
msgid "identity email address"
|
||||
msgstr "διεύθυνση email ταυτότητας"
|
||||
|
||||
#: build/lib/core/models.py:160 core/models.py:160
|
||||
msgid "admin email address"
|
||||
msgstr "διεύθυνση email διαχειριστή"
|
||||
|
||||
#: build/lib/core/models.py:167 core/models.py:167
|
||||
msgid "language"
|
||||
msgstr "γλώσσα"
|
||||
|
||||
#: build/lib/core/models.py:168 core/models.py:168
|
||||
msgid "The language in which the user wants to see the interface."
|
||||
msgstr "Η γλώσσα στην οποία ο χρήστης θέλει να δει τη διεπαφή."
|
||||
|
||||
#: build/lib/core/models.py:176 core/models.py:176
|
||||
msgid "The timezone in which the user wants to see times."
|
||||
msgstr "Η ζώνη ώρας στην οποία ο χρήστης θέλει να βλέπει την ώρα."
|
||||
|
||||
#: build/lib/core/models.py:179 core/models.py:179
|
||||
msgid "device"
|
||||
msgstr "συσκευή"
|
||||
|
||||
#: build/lib/core/models.py:181 core/models.py:181
|
||||
msgid "Whether the user is a device or a real user."
|
||||
msgstr "Εάν ο χρήστης είναι μια συσκευή ή πραγματικός χρήστης."
|
||||
|
||||
#: build/lib/core/models.py:184 core/models.py:184
|
||||
msgid "staff status"
|
||||
msgstr "κατάσταση προσωπικού"
|
||||
|
||||
#: build/lib/core/models.py:186 core/models.py:186
|
||||
msgid "Whether the user can log into this admin site."
|
||||
msgstr "Εάν ο χρήστης μπορεί να συνδεθεί σε αυτόν τον ιστότοπο διαχείρισης."
|
||||
|
||||
#: build/lib/core/models.py:189 core/models.py:189
|
||||
msgid "active"
|
||||
msgstr "ενεργός"
|
||||
|
||||
#: build/lib/core/models.py:192 core/models.py:192
|
||||
msgid "Whether this user should be treated as active. Unselect this instead of deleting accounts."
|
||||
msgstr "Εάν αυτός ο χρήστης πρέπει να θεωρείται ενεργός. Αποεπιλέξτε το αντί να διαγράψετε λογαριασμούς."
|
||||
|
||||
#: build/lib/core/models.py:197 core/models.py:197
|
||||
msgid "first connection status"
|
||||
msgstr "πρώτη κατάσταση σύνδεσης"
|
||||
|
||||
#: build/lib/core/models.py:199 core/models.py:199
|
||||
msgid "Whether the user has completed the first connection process."
|
||||
msgstr "Εάν ο χρήστης έχει ολοκληρώσει τη διαδικασία της πρώτης σύνδεσης."
|
||||
|
||||
#: build/lib/core/models.py:209 core/models.py:209
|
||||
msgid "user"
|
||||
msgstr "χρήστης"
|
||||
|
||||
#: build/lib/core/models.py:210 core/models.py:210
|
||||
msgid "users"
|
||||
msgstr "χρήστες"
|
||||
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr "Ενεργή διεύθυνση email"
|
||||
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Διεύθυνση email για απενεργοποίηση"
|
||||
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Μοναδικό αναγνωριστικό στο πηγαίο αρχείο"
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "Σε εκκρεμότητα"
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Έτοιμο"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Done"
|
||||
msgstr "Ολοκληρώθηκε"
|
||||
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr "Σφάλμα"
|
||||
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr "συμφωνία χρήστη"
|
||||
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr "συμφωνία χρηστών"
|
||||
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr "Έχετε ζητήσει έναν συνδυασμό των λογαριασμών χρήστη σας στα Έγγραφα.\n"
|
||||
" Για να επιβεβαιώσετε ότι είστε εκείνος που ξεκίνησε το αίτημα\n"
|
||||
" και ότι αυτό το email ανήκει σε σας:"
|
||||
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Επιβεβαιώστε κάνοντας κλικ στο σύνδεσμο για να ξεκινήσει η συμφωνία"
|
||||
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr "Κάντε κλικ εδώ"
|
||||
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr "Επιβεβαίωση"
|
||||
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Το αίτημά σας για συμφωνία έχει επεξεργαστεί.\n"
|
||||
" Νέα έγγραφα είναι πιθανό να σχετίζονται με τον λογαριασμό σας:"
|
||||
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Οι λογαριασμοί σας έχουν συγχωνευθεί"
|
||||
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr "Κάντε κλικ εδώ για να δείτε"
|
||||
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr "Δείτε τα έγγραφά μου"
|
||||
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr "Αρχείο CSV"
|
||||
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr "Εκτελείται"
|
||||
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "εισαγωγή CSV συμφωνίας χρηστών"
|
||||
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "εισαγωγές CSV συμφωνίας χρηστών"
|
||||
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Please check for typos.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr "Το αίτημά σας για επαλήθευση δεν ολοκληρώθηκε με επιτυχία.\n"
|
||||
" Η επαλήθευση απέτυχε για τις ακόλουθες διευθύνσεις email:\n"
|
||||
" {recipient_email}, {other_email}.\n"
|
||||
" Παρακαλούμε ελέγξτε αν υπάρχουν τυπογραφικά λάθη.\n"
|
||||
" Μπορείτε να υποβάλετε ένα νέο αίτημα με τις σωστές διευθύνσεις email."
|
||||
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Η συμφωνία των λογαριασμών σας Docs δεν ολοκληρώθηκε"
|
||||
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr "Κάντε ένα νέο αίτημα"
|
||||
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "τίτλος"
|
||||
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "απόσπασμα"
|
||||
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Έγγραφο"
|
||||
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Έγγραφα"
|
||||
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Έγγραφο χωρίς τίτλο"
|
||||
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Άνοιγμα"
|
||||
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "Ο/Η {name} μοιράστηκε ένα έγγραφο μαζί σας!"
|
||||
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "Ο/Η {name} σας προσκάλεσε με τον ρόλο \"{role}\" στο ακόλουθο έγγραφο:"
|
||||
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "Ο/Η {name} μοιράστηκε ένα έγγραφο μαζί σας: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Ίχνος συνδέσμου εγγράφου/χρήστη"
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Ίχνη συνδέσμου εγγράφου/χρήστη"
|
||||
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Ένα ίχνος συνδέσμου υπάρχει ήδη για αυτό το έγγραφο/χρήστη."
|
||||
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Αγαπημένο έγγραφο"
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Αγαπημένα έγγραφα"
|
||||
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Αυτό το έγγραφο στοχεύεται ήδη από μια σχέση αγαπημένου για τον ίδιο χρήστη."
|
||||
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Σχέση εγγράφου/χρήστη"
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Σχέσεις εγγράφου/χρήστη"
|
||||
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Αυτός ο χρήστης συμμετέχει ήδη σε αυτό το έγγραφο."
|
||||
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Αυτή η ομάδα συμμετέχει ήδη σε αυτό το έγγραφο."
|
||||
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Πρέπει να οριστεί είτε χρήστης είτε ομάδα, όχι και τα δύο."
|
||||
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Αίτημα πρόσβασης σε έγγραφο"
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Αιτήματα πρόσβασης σε έγγραφα"
|
||||
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Αυτός ο χρήστης έχει ήδη ζητήσει πρόσβαση σε αυτό το έγγραφο."
|
||||
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "Ο/Η {name} θα ήθελε πρόσβαση σε ένα έγγραφο!"
|
||||
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "Ο/Η {name} θα ήθελε πρόσβαση στο ακόλουθο έγγραφο:"
|
||||
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "Ο/Η {name} ζητά πρόσβαση στο έγγραφο: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "Νήμα"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr "Νήματα"
|
||||
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr "Ανώνυμος"
|
||||
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Σχόλιο"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr "Σχόλια"
|
||||
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Αυτό το emoji έχει χρησιμοποιηθεί ήδη ως αντίδραση σε αυτό το σχόλιο."
|
||||
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr "Αντίδραση"
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr "Αντιδράσεις"
|
||||
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "διεύθυνση email"
|
||||
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Πρόσκληση σε έγγραφο"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Προσκλήσεις εγγράφου"
|
||||
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Αυτό το email σχετίζεται ήδη με έναν εγγεγραμμένο χρήστη."
|
||||
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr "Τεχνητή Νοημοσύνη (AI) Docs"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
msgid "Logo email"
|
||||
msgstr "Λογότυπο email"
|
||||
|
||||
#: core/templates/mail/html/template.html:219
|
||||
#: core/templates/mail/text/template.txt:14
|
||||
msgid " Docs, your new essential tool for organizing, sharing and collaborating on your documents as a team. "
|
||||
msgstr " Docs, το νέο απαραίτητο εργαλείο σας για την οργάνωση, τον διαμοιρασμό και τη συνεργασία στα έγγραφά σας ως ομάδα. "
|
||||
|
||||
#: core/templates/mail/html/template.html:226
|
||||
#: core/templates/mail/text/template.txt:16
|
||||
#, python-format
|
||||
msgid " Brought to you by %(brandname)s "
|
||||
msgstr " Σας προσφέρεται από την %(brandname)s "
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: English\n"
|
||||
"Language: en_US\n"
|
||||
@@ -46,36 +46,40 @@ msgstr ""
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -247,98 +251,98 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Estructura en árbol"
|
||||
msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr "Buscar"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Yo soy el creador"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Enmascarado"
|
||||
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "¡Un nuevo documento se ha creado por ti!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Se le ha concedido la propiedad de un nuevo documento :"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia de {title}"
|
||||
@@ -247,98 +251,98 @@ msgstr "usuario"
|
||||
msgid "users"
|
||||
msgstr "usuarios"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "Pending"
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Listo"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
msgstr "Terminado"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
msgstr "Error"
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
msgstr "Haga click aquí"
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
msgstr "Confirmar"
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
msgstr "Haz clic aquí para ver"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
msgstr "Archivo CSV"
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
msgstr "En ejecución"
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,177 +351,177 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
msgstr "Hacer un nuevo pedido"
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "título"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "resumen"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Documentos"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento sin título"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Abrir"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "¡{name} ha compartido un documento contigo!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} ha compartido un documento contigo: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Traza del enlace de documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Trazas del enlace de documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
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:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento favorito"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Documentos favoritos"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
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:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relación documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relaciones documento/usuario"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Este usuario ya forma parte del documento."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Este equipo ya forma parte del documento."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
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:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Solicitud de acceso"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Solicitud de accesos"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
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:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "¡{name} desea acceder a un documento!"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} desea acceso al siguiente documento:"
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} está pidiendo acceso al documento: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
msgstr "Thread"
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
msgstr "Threads"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
msgstr "Anónimo"
|
||||
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Comentario"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
msgstr "Comentarios"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
msgstr "Reacción"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
msgstr "Reacciones"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "dirección de correo electrónico"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitación al documento"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitaciones a documentos"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Este correo electrónico está asociado a un usuario registrado."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
msgstr "Docs AI"
|
||||
|
||||
#: core/templates/mail/html/template.html:153
|
||||
#: core/templates/mail/text/template.txt:3
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr_FR\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Arborescence"
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr "Recherche"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Je suis l'auteur"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Masqué"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Favoris"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nouveau document a été créé pour vous !"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Vous avez été déclaré propriétaire d'un nouveau document :"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "Ce champ est obligatoire."
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "La portée du lien '%(link_reach)s' n'est pas autorisée en fonction de la configuration du document parent."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copie de {title}"
|
||||
@@ -247,46 +251,46 @@ msgstr "utilisateur"
|
||||
msgid "users"
|
||||
msgstr "utilisateurs"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr "Adresse email active"
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Adresse email à désactiver"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Identifiant unique dans le fichier source"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "En attente"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Prêt"
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Done"
|
||||
msgstr "Terminé"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr "Erreur"
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr "rapprochement de l'utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr "rapprochements de l'utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
@@ -294,54 +298,54 @@ msgstr "Vous avez demandé un rapprochement de vos comptes utilisateur sur Docs.
|
||||
" Pour confirmer que vous êtes bien à l'origine de cette demande\n"
|
||||
" et que cet e-mail vous appartient :"
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Confirmez en cliquant sur le lien pour commencer le rapprochement"
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr "Cliquez ici"
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmer"
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Votre demande de rapprochement a été traitée.\n"
|
||||
" De nouveaux documents sont probablement associés à votre compte :"
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Vos comptes ont été fusionnés"
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr "Cliquez ici pour voir"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr "Voir mes documents"
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr "Fichier CSV"
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr "En cours"
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "importation CSV de rapprochement utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "importations CSV de rapprochement utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -354,175 +358,175 @@ msgstr "Votre demande de rapprochement n'a pas abouti.\n"
|
||||
" Veuillez vérifier qu'il n'y a pas de fautes de frappe.\n"
|
||||
" Vous pouvez envoyer une nouvelle demande avec des adresses e-mail valides."
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Le rapprochement de vos comptes Docs n'est pas terminé"
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr "Faire une nouvelle demande"
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "titre"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "extrait"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Documents"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Document sans titre"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Ouvrir"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} a partagé un document avec vous!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, 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:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Trace du lien document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Traces du lien document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
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:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favori"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Documents favoris"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
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:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Relation document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Relations document/utilisateur"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Cet utilisateur est déjà dans ce document."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Cette équipe est déjà dans ce document."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
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:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Demande d'accès au document"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
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:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} souhaiterait accéder au document suivant !"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, 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:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, 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:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "Conversation"
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr "Conversations"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr "Anonyme"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Commentaire"
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr "Commentaires"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Cet émoji a déjà été réagi à ce commentaire."
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr "Réaction"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr "Réactions"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "adresse e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Invitation à un document"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Invitations à un document"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Cette adresse email est déjà associée à un utilisateur inscrit."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs IA"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it_IT\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Struttura ad albero"
|
||||
msgid "Title"
|
||||
msgstr "Titolo"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Il creatore sono io"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Preferiti"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Un nuovo documento è stato creato a tuo nome!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Sei ora proprietario di un nuovo documento:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "copia di {title}"
|
||||
@@ -247,98 +251,98 @@ msgstr "utente"
|
||||
msgid "users"
|
||||
msgstr "utenti"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "titolo"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Documento"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Documenti"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Documento senza titolo"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Apri"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} ha condiviso un documento con te!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, 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:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Documento preferito"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Documenti preferiti"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Questo utente è già presente in questo documento."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Questo team è già presente in questo documento."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "indirizzo e-mail"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Invito al documento"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Inviti al documento"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Questa email è già associata a un utente registrato."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl_NL\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Boomstructuur"
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Ik ben eigenaar"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Gemaskeerd"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriet"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Een nieuw document is namens u gemaakt!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "U heeft eigenaarschap van een nieuw document gekregen:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "Dit veld is verplicht."
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Link bereik '%(link_reach)s' is niet toegestaan op basis van bovenliggende documentconfiguratie."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "kopie van {title}"
|
||||
@@ -247,46 +251,46 @@ msgstr "gebruiker"
|
||||
msgid "users"
|
||||
msgstr "gebruikers"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr "Actieve e-mail adres"
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "E-mailadres om te deactiveren"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Unieke ID in het bronbestand"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "In behandeling"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Klaar"
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Done"
|
||||
msgstr "Klaar"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr "Fout"
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr "gebruiker samenvoegen"
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr "gebruikers samenvoegen"
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
@@ -294,54 +298,54 @@ msgstr "Je hebt gevraagd om een samenvoeging van je gebruikersaccounts op Docs.\
|
||||
" Om te bevestigen dat u degene bent die het verzoek\n"
|
||||
" heeft geïnitieerd en dat deze e-mail van u is:"
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Bevestig door te klikken op de link om de samenvoeging te starten"
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr "Klik hier"
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr "Bevestig"
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Uw samenvoegingsverzoek is verwerkt.\n"
|
||||
" Nieuwe documenten worden waarschijnlijk geassocieerd met uw account:"
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Je accounts zijn samengevoegd"
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr "Klik hier om te bekijken"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr "Mijn documenten bekijken"
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr "CSV bestand"
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr "Bezig"
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "gebruiker samenvoeging CSV import"
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "gebruiker reconciliation CSV imports"
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -354,175 +358,175 @@ msgstr "Uw verzoek tot verzoening is mislukt.\n"
|
||||
" Controleer op typefouten.\n"
|
||||
" U kunt een ander verzoek indienen met de geldige e-mailadressen."
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Samenvoeging van je Docs accounts is niet voltooid"
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr "Maak een nieuw verzoek"
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "titel"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "uittreksel"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Document"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Documenten"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Naamloos Document"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Open"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} heeft een document met u gedeeld!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, 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:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Document/gebruiker link"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Document/gebruiker link"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Een link bestaat al voor dit document/deze gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Document favoriet"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Document favorieten"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Dit document is al in gebruik als favoriet door dezelfde gebruiker."
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Document/gebruiker relatie"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Document/gebruiker relaties"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "De gebruiker bestaat al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Dit team bestaat al in dit document."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
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:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Document verzoekt om toegang"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Document verzoekt om toegangen"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Deze gebruiker heeft al om toegang tot dit document gevraagd."
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} verzoekt toegang tot een document!"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} verzoekt toegang tot het volgende document:"
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} verzoekt toegang tot het document: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "Kanaal"
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr "Kanalen"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr "Anoniem"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Reactie"
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr "Reacties"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Deze emoji is al op deze opmerking gereageerd."
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr "Reactie"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr "Reacties"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "e-mailadres"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Document uitnodiging"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Document uitnodigingen"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Deze email is al geassocieerd met een geregistreerde gebruiker."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs AI"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt_PT\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Estrutura de árvore"
|
||||
msgid "Title"
|
||||
msgstr "Título"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Eu sou o criador"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Favorito"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Um novo documento foi criado em seu nome!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "A propriedade de um novo documento foi concedida a você:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "cópia de {title}"
|
||||
@@ -137,7 +141,7 @@ msgstr ""
|
||||
|
||||
#: build/lib/core/enums.py:40 core/enums.py:40
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
msgstr "Esquerda"
|
||||
|
||||
#: build/lib/core/enums.py:41 core/enums.py:41
|
||||
msgid "Right"
|
||||
@@ -145,7 +149,7 @@ msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:80 core/models.py:80
|
||||
msgid "id"
|
||||
msgstr ""
|
||||
msgstr "id"
|
||||
|
||||
#: build/lib/core/models.py:81 core/models.py:81
|
||||
msgid "primary key for the record as UUID"
|
||||
@@ -173,7 +177,7 @@ msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:141 core/models.py:141
|
||||
msgid "sub"
|
||||
msgstr ""
|
||||
msgstr "sub"
|
||||
|
||||
#: build/lib/core/models.py:142 core/models.py:142
|
||||
msgid "Required. 255 characters or fewer. ASCII characters only."
|
||||
@@ -241,104 +245,104 @@ msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:209 core/models.py:209
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
msgstr "utilizador"
|
||||
|
||||
#: build/lib/core/models.py:210 core/models.py:210
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
msgstr "Concluído"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
msgstr "Abrir"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Language: ru_RU\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Древовидная структура"
|
||||
msgid "Title"
|
||||
msgstr "Заголовок"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Создатель - я"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Скрытый"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Избранное"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Новый документ был создан от вашего имени!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Вы назначены владельцем для нового документа:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "Это поле обязательное."
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Доступ по ссылке '%(link_reach)s' запрещён в соответствии с настройками родительского документа."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "копия {title}"
|
||||
@@ -247,46 +251,46 @@ msgstr "пользователь"
|
||||
msgid "users"
|
||||
msgstr "пользователи"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr "Активный адрес электронной почты"
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Адрес электронной почты для деактивации"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Уникальный идентификатор в исходном файле"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "В обработке"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Готово"
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Done"
|
||||
msgstr "Выполнено"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr "Ошибка"
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr "сверка данных пользователя"
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr "сверки данных пользователя"
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
@@ -294,54 +298,54 @@ msgstr "Вы запросили сверку учётных записей по
|
||||
" Чтобы подтвердить факт того, что вы являетесь инициатором запроса\n"
|
||||
" и что этот адрес принадлежит вам:"
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Чтобы начать сверку, подтвердите это, нажав на ссылку"
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr "Нажмите здесь"
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr "Подтверждение"
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Ваш запрос на сверку был обработан.\n"
|
||||
" Новые документы, вероятно, связаны с вашей учётной записью:"
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Ваши учётные записи были объединены"
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr "Нажмите здесь, чтобы просмотреть"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr "Просмотреть мои документы"
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr "CSV-файл"
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr "Выполнение"
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "импорт из CSV сверки пользователей"
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "импорты из CSV сверки пользователями"
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -354,175 +358,175 @@ msgstr "Ваш запрос на сверку не удался.\n"
|
||||
" Пожалуйста, проверьте, нет ли в них опечаток.\n"
|
||||
" Вы можете отправить ещё один запрос с действительными адресами электронной почты."
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Сверка ваших учётных записей Docs не завершена"
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr "Создать новый запрос"
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "заголовок"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "отрывок"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Документ"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Документы"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Безымянный документ"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Открыть"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} делится с вами документом!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} приглашает вас присоединиться к следующему документу с ролью \"{role}\":"
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} делится с вами документом: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Трассировка связи документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Трассировка связей документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Для этого документа/пользователя уже существует трассировка ссылки."
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Избранный документ"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Избранные документы"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Этот документ уже помечен как избранный для этого пользователя."
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Отношение документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Отношения документ/пользователь"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Этот пользователь уже имеет доступ к этому документу."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Эта команда уже имеет доступ к этому документу."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Может быть выбран либо пользователь, либо команда, но не оба варианта сразу."
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Документ запрашивает доступ"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Документ запрашивает доступы"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Этот пользователь уже запросил доступ к этому документу."
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} хочет получить доступ к документу!"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} хочет получить доступ к следующему документу:"
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} запрашивает доступ к документу: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "Обсуждение"
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr "Обсуждения"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr "Аноним"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Комментарий"
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr "Комментарии"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Этот эмодзи уже использован в этом комментарии."
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr "Реакция"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr "Реакции"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "адрес электронной почты"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Приглашение для документа"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Приглашения для документов"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Этот адрес уже связан с зарегистрированным пользователем."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs ИИ"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"Language: sl_SI\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Drevesna struktura"
|
||||
msgid "Title"
|
||||
msgstr "Naslov"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Ustvaril sem jaz"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Priljubljena"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Nov dokument je bil ustvarjen v vašem imenu!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Dodeljeno vam je bilo lastništvo nad novim dokumentom:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -247,98 +251,98 @@ msgstr "uporabnik"
|
||||
msgid "users"
|
||||
msgstr "uporabniki"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "naslov"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "odlomek"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Dokument"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Dokument brez naslova"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Odpri"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} je delil dokument z vami!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, 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:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} je delil dokument z vami: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Dokument/sled povezave uporabnika"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Sledi povezav dokumenta/uporabnika"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Za ta dokument/uporabnika že obstaja sled povezave."
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Priljubljeni dokument"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Priljubljeni dokumenti"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
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:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Odnos dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Odnosi dokument/uporabnik"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Ta uporabnik je že v tem dokumentu."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ta ekipa je že v tem dokumentu."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
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:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "elektronski naslov"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Vabilo na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Vabila na dokument"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ta e-poštni naslov je že povezan z registriranim uporabnikom."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv_SE\n"
|
||||
@@ -46,36 +46,40 @@ msgstr ""
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Skaparen är jag"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Favoriter"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Ett nytt dokument skapades åt dig!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Du har beviljats äganderätt till ett nytt dokument:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -247,98 +251,98 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Öppna"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "e-postadress"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Bjud in dokument"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Inbjudningar dokument"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Denna e-postadress är redan associerad med en registrerad användare."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Language: tr_TR\n"
|
||||
@@ -46,36 +46,40 @@ msgstr ""
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
msgid "Creator is me"
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Masked"
|
||||
msgid "Creator is me"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr ""
|
||||
@@ -247,98 +251,98 @@ msgstr ""
|
||||
msgid "users"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"Language: uk_UA\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "Ієрархічна структура"
|
||||
msgid "Title"
|
||||
msgstr "Заголовок"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr "Пошук"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "Творець — я"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "Приховано"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "Обране"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "Новий документ був створений від вашого імені!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "Ви тепер є власником нового документа:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "Це поле є обов’язковим."
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "Доступ до посилання '%(link_reach)s' заборонено на основі конфігурації батьківського документа."
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "копія {title}"
|
||||
@@ -247,46 +251,46 @@ msgstr "користувач"
|
||||
msgid "users"
|
||||
msgstr "користувачі"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr "Активна електронна адреса"
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr "Електронна адреса, що буде деактивована"
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr "Унікальний ідентифікатор у вихідному файлі"
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr "В очікуванні"
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr "Готово"
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Done"
|
||||
msgstr "Виконано"
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr "Помилка"
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr "узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr "узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
@@ -294,54 +298,54 @@ msgstr "Ви запросили узгодження своїх облікови
|
||||
" Щоб підтвердити, що саме ви ініціювали запит\n"
|
||||
" і що ця електронна адреса належить вам:"
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr "Підтвердіть, натиснувши на посилання, щоб почати узгодження"
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr "Натисніть тут"
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr "Підтвердження"
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr "Ваш запит на узгодження оброблено.\n"
|
||||
" Нові документи, ймовірно, пов'язані з вашим обліковим записом:"
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr "Ваші облікові записи були об'єднані"
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr "Натисніть тут, щоб переглянути"
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr "Переглянути мої документи"
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr "CSV-файл"
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr "Виконується"
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr "імпорт CSV для узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr "імпорт CSV для узгодження користувачів"
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -354,175 +358,175 @@ msgstr "Ваш запит на узгодження не був виконани
|
||||
" Перевірте, чи немає помилок.\n"
|
||||
" Ви можете надіслати інший запит із дійсними адресами електронної пошти."
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr "Узгодження ваших облікових записів не завершено"
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr "Зробити новий запит"
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "заголовок"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "уривок"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "Документ"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "Документи"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "Документ без назви"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "Відкрити"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} ділиться з вами документом!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} запрошує вас для роботи з документом із роллю \"{role}\":"
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} ділиться з вами документом: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "Трасування посилання Документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "Трасування посилань Документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "Відстеження вже існуючих посилань для цього документа/користувача."
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "Обраний документ"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "Обрані документи"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "Цей документ вже вказаний як обраний для одного користувача."
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "Відносини документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "Відносини документ/користувач"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "Цей користувач вже має доступ до цього документу."
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "Ця команда вже має доступ до цього документа."
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "Вкажіть користувача або команду, а не обох."
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "Запит доступу до документа"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "Запит доступу для документа"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "Цей користувач вже попросив доступ до цього документа."
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} хоче отримати доступ до документа!"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} бажає отримати доступ до наступного документа:"
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} запитує доступ до документа: {title}"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "Обговорення"
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr "Обговорення"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr "Анонім"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "Коментар"
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr "Коментарі"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "Цим емодзі вже відреагували на цей коментар."
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr "Реакція"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr "Реакції"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "електронна адреса"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "Запрошення до редагування документа"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "Запрошення до редагування документів"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "Ця електронна пошта вже пов'язана з зареєстрованим користувачем."
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr "Docs ШІ"
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: lasuite-docs\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-03-12 13:31+0000\n"
|
||||
"PO-Revision-Date: 2026-03-13 16:53\n"
|
||||
"POT-Creation-Date: 2026-03-25 16:42+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 16:55\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh_CN\n"
|
||||
@@ -46,36 +46,40 @@ msgstr "樹狀結構"
|
||||
msgid "Title"
|
||||
msgstr "標題"
|
||||
|
||||
#: build/lib/core/api/filters.py:62 core/api/filters.py:62
|
||||
#: build/lib/core/api/filters.py:51 core/api/filters.py:51
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
msgid "Creator is me"
|
||||
msgstr "建立者是我"
|
||||
|
||||
#: build/lib/core/api/filters.py:65 core/api/filters.py:65
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
msgid "Masked"
|
||||
msgstr "已隱藏"
|
||||
|
||||
#: build/lib/core/api/filters.py:68 core/api/filters.py:68
|
||||
#: build/lib/core/api/filters.py:71 core/api/filters.py:71
|
||||
msgid "Favorite"
|
||||
msgstr "我的最愛"
|
||||
|
||||
#: build/lib/core/api/serializers.py:526 core/api/serializers.py:526
|
||||
#: build/lib/core/api/serializers.py:535 core/api/serializers.py:535
|
||||
msgid "A new document was created on your behalf!"
|
||||
msgstr "已代表您建立新文件!"
|
||||
|
||||
#: build/lib/core/api/serializers.py:530 core/api/serializers.py:530
|
||||
#: build/lib/core/api/serializers.py:539 core/api/serializers.py:539
|
||||
msgid "You have been granted ownership of a new document:"
|
||||
msgstr "您已獲得新文件的所有權:"
|
||||
|
||||
#: build/lib/core/api/serializers.py:566 core/api/serializers.py:566
|
||||
#: build/lib/core/api/serializers.py:575 core/api/serializers.py:575
|
||||
msgid "This field is required."
|
||||
msgstr "此欄位為必填。"
|
||||
|
||||
#: build/lib/core/api/serializers.py:577 core/api/serializers.py:577
|
||||
#: build/lib/core/api/serializers.py:586 core/api/serializers.py:586
|
||||
#, python-format
|
||||
msgid "Link reach '%(link_reach)s' is not allowed based on parent document configuration."
|
||||
msgstr "根據父文件設定,不允許連結範圍「%(link_reach)s」。"
|
||||
|
||||
#: build/lib/core/api/viewsets.py:1298 core/api/viewsets.py:1298
|
||||
#: build/lib/core/api/viewsets.py:1315 core/api/viewsets.py:1315
|
||||
#, python-brace-format
|
||||
msgid "copy of {title}"
|
||||
msgstr "{title} 的副本"
|
||||
@@ -247,98 +251,98 @@ msgstr "使用者"
|
||||
msgid "users"
|
||||
msgstr "使用者"
|
||||
|
||||
#: build/lib/core/models.py:378 core/models.py:378
|
||||
#: build/lib/core/models.py:376 core/models.py:376
|
||||
msgid "Active email address"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:379 core/models.py:379
|
||||
#: build/lib/core/models.py:377 core/models.py:377
|
||||
msgid "Email address to deactivate"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:406 core/models.py:406
|
||||
#: build/lib/core/models.py:404 core/models.py:404
|
||||
msgid "Unique ID in the source file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:410 build/lib/core/models.py:708 core/models.py:410
|
||||
#: core/models.py:708
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:411 core/models.py:411
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:412 build/lib/core/models.py:710 core/models.py:412
|
||||
#: core/models.py:710
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:413 core/models.py:413
|
||||
msgid "Ready"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:414 build/lib/core/models.py:712 core/models.py:414
|
||||
#: core/models.py:712
|
||||
msgid "Done"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:415 build/lib/core/models.py:713 core/models.py:415
|
||||
#: core/models.py:713
|
||||
#: build/lib/core/models.py:413 build/lib/core/models.py:711 core/models.py:413
|
||||
#: core/models.py:711
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:423 core/models.py:423
|
||||
#: build/lib/core/models.py:421 core/models.py:421
|
||||
msgid "user reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:424 core/models.py:424
|
||||
#: build/lib/core/models.py:422 core/models.py:422
|
||||
msgid "user reconciliations"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:662 core/models.py:662
|
||||
#: build/lib/core/models.py:660 core/models.py:660
|
||||
msgid "You have requested a reconciliation of your user accounts on Docs.\n"
|
||||
" To confirm that you are the one who initiated the request\n"
|
||||
" and that this email belongs to you:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:668 core/models.py:668
|
||||
#: build/lib/core/models.py:666 core/models.py:666
|
||||
msgid "Confirm by clicking the link to start the reconciliation"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:673 build/lib/core/models.py:779 core/models.py:673
|
||||
#: core/models.py:779
|
||||
#: build/lib/core/models.py:671 build/lib/core/models.py:777 core/models.py:671
|
||||
#: core/models.py:777
|
||||
msgid "Click here"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:674 core/models.py:674
|
||||
#: build/lib/core/models.py:672 core/models.py:672
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:685 core/models.py:685
|
||||
#: build/lib/core/models.py:683 core/models.py:683
|
||||
msgid "Your reconciliation request has been processed.\n"
|
||||
" New documents are likely associated with your account:"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:690 core/models.py:690
|
||||
#: build/lib/core/models.py:688 core/models.py:688
|
||||
msgid "Your accounts have been merged"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:695 core/models.py:695
|
||||
#: build/lib/core/models.py:693 core/models.py:693
|
||||
msgid "Click here to see"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:696 core/models.py:696
|
||||
#: build/lib/core/models.py:694 core/models.py:694
|
||||
msgid "See my documents"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:706 core/models.py:706
|
||||
#: build/lib/core/models.py:704 core/models.py:704
|
||||
msgid "CSV file"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:711 core/models.py:711
|
||||
#: build/lib/core/models.py:709 core/models.py:709
|
||||
msgid "Running"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:721 core/models.py:721
|
||||
#: build/lib/core/models.py:719 core/models.py:719
|
||||
msgid "user reconciliation CSV import"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:722 core/models.py:722
|
||||
#: build/lib/core/models.py:720 core/models.py:720
|
||||
msgid "user reconciliation CSV imports"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:766 core/models.py:766
|
||||
#: build/lib/core/models.py:764 core/models.py:764
|
||||
#, python-brace-format
|
||||
msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" Reconciliation failed for the following email addresses:\n"
|
||||
@@ -347,175 +351,175 @@ msgid "Your request for reconciliation was unsuccessful.\n"
|
||||
" You can submit another request with the valid email addresses."
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:774 core/models.py:774
|
||||
#: build/lib/core/models.py:772 core/models.py:772
|
||||
msgid "Reconciliation of your Docs accounts not completed"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:780 core/models.py:780
|
||||
#: build/lib/core/models.py:778 core/models.py:778
|
||||
msgid "Make a new request"
|
||||
msgstr ""
|
||||
|
||||
#: build/lib/core/models.py:879 core/models.py:879
|
||||
#: build/lib/core/models.py:877 core/models.py:877
|
||||
msgid "title"
|
||||
msgstr "標題"
|
||||
|
||||
#: build/lib/core/models.py:880 core/models.py:880
|
||||
#: build/lib/core/models.py:878 core/models.py:878
|
||||
msgid "excerpt"
|
||||
msgstr "摘要"
|
||||
|
||||
#: build/lib/core/models.py:929 core/models.py:929
|
||||
#: build/lib/core/models.py:927 core/models.py:927
|
||||
msgid "Document"
|
||||
msgstr "文件"
|
||||
|
||||
#: build/lib/core/models.py:930 core/models.py:930
|
||||
#: build/lib/core/models.py:928 core/models.py:928
|
||||
msgid "Documents"
|
||||
msgstr "文件"
|
||||
|
||||
#: build/lib/core/models.py:942 build/lib/core/models.py:1346
|
||||
#: core/models.py:942 core/models.py:1346
|
||||
#: build/lib/core/models.py:940 build/lib/core/models.py:1345
|
||||
#: core/models.py:940 core/models.py:1345
|
||||
msgid "Untitled Document"
|
||||
msgstr "未命名文件"
|
||||
|
||||
#: build/lib/core/models.py:1347 core/models.py:1347
|
||||
#: build/lib/core/models.py:1346 core/models.py:1346
|
||||
msgid "Open"
|
||||
msgstr "開啟"
|
||||
|
||||
#: build/lib/core/models.py:1382 core/models.py:1382
|
||||
#: build/lib/core/models.py:1381 core/models.py:1381
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you!"
|
||||
msgstr "{name} 與您分享了一份文件!"
|
||||
|
||||
#: build/lib/core/models.py:1386 core/models.py:1386
|
||||
#: build/lib/core/models.py:1385 core/models.py:1385
|
||||
#, python-brace-format
|
||||
msgid "{name} invited you with the role \"{role}\" on the following document:"
|
||||
msgstr "{name} 邀請您以「{role}」角色參與以下文件:"
|
||||
|
||||
#: build/lib/core/models.py:1392 core/models.py:1392
|
||||
#: build/lib/core/models.py:1391 core/models.py:1391
|
||||
#, python-brace-format
|
||||
msgid "{name} shared a document with you: {title}"
|
||||
msgstr "{name} 與您分享了一份文件:{title}"
|
||||
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
#: build/lib/core/models.py:1492 core/models.py:1492
|
||||
msgid "Document/user link trace"
|
||||
msgstr "文件/使用者連結追蹤"
|
||||
|
||||
#: build/lib/core/models.py:1494 core/models.py:1494
|
||||
#: build/lib/core/models.py:1493 core/models.py:1493
|
||||
msgid "Document/user link traces"
|
||||
msgstr "文件/使用者連結追蹤"
|
||||
|
||||
#: build/lib/core/models.py:1500 core/models.py:1500
|
||||
#: build/lib/core/models.py:1499 core/models.py:1499
|
||||
msgid "A link trace already exists for this document/user."
|
||||
msgstr "此文件/使用者已存在連結追蹤。"
|
||||
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
#: build/lib/core/models.py:1522 core/models.py:1522
|
||||
msgid "Document favorite"
|
||||
msgstr "文件收藏"
|
||||
|
||||
#: build/lib/core/models.py:1524 core/models.py:1524
|
||||
#: build/lib/core/models.py:1523 core/models.py:1523
|
||||
msgid "Document favorites"
|
||||
msgstr "文件收藏"
|
||||
|
||||
#: build/lib/core/models.py:1530 core/models.py:1530
|
||||
#: build/lib/core/models.py:1529 core/models.py:1529
|
||||
msgid "This document is already targeted by a favorite relation instance for the same user."
|
||||
msgstr "此使用者已將此文件加入收藏。"
|
||||
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
#: build/lib/core/models.py:1551 core/models.py:1551
|
||||
msgid "Document/user relation"
|
||||
msgstr "文件/使用者關聯"
|
||||
|
||||
#: build/lib/core/models.py:1553 core/models.py:1553
|
||||
#: build/lib/core/models.py:1552 core/models.py:1552
|
||||
msgid "Document/user relations"
|
||||
msgstr "文件/使用者關聯"
|
||||
|
||||
#: build/lib/core/models.py:1559 core/models.py:1559
|
||||
#: build/lib/core/models.py:1558 core/models.py:1558
|
||||
msgid "This user is already in this document."
|
||||
msgstr "此使用者已在此文件中。"
|
||||
|
||||
#: build/lib/core/models.py:1565 core/models.py:1565
|
||||
#: build/lib/core/models.py:1564 core/models.py:1564
|
||||
msgid "This team is already in this document."
|
||||
msgstr "此團隊已在此文件中。"
|
||||
|
||||
#: build/lib/core/models.py:1571 core/models.py:1571
|
||||
#: build/lib/core/models.py:1570 core/models.py:1570
|
||||
msgid "Either user or team must be set, not both."
|
||||
msgstr "必須設定使用者或團隊其中之一,不能同時設定兩者。"
|
||||
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
#: build/lib/core/models.py:1721 core/models.py:1721
|
||||
msgid "Document ask for access"
|
||||
msgstr "要求文件存取權"
|
||||
|
||||
#: build/lib/core/models.py:1723 core/models.py:1723
|
||||
#: build/lib/core/models.py:1722 core/models.py:1722
|
||||
msgid "Document ask for accesses"
|
||||
msgstr "要求文件存取權"
|
||||
|
||||
#: build/lib/core/models.py:1729 core/models.py:1729
|
||||
#: build/lib/core/models.py:1728 core/models.py:1728
|
||||
msgid "This user has already asked for access to this document."
|
||||
msgstr "此使用者已要求過存取此文件的權限。"
|
||||
|
||||
#: build/lib/core/models.py:1786 core/models.py:1786
|
||||
#: build/lib/core/models.py:1785 core/models.py:1785
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to a document!"
|
||||
msgstr "{name} 想要存取文件!"
|
||||
|
||||
#: build/lib/core/models.py:1790 core/models.py:1790
|
||||
#: build/lib/core/models.py:1789 core/models.py:1789
|
||||
#, python-brace-format
|
||||
msgid "{name} would like access to the following document:"
|
||||
msgstr "{name} 想要存取以下文件:"
|
||||
|
||||
#: build/lib/core/models.py:1796 core/models.py:1796
|
||||
#: build/lib/core/models.py:1795 core/models.py:1795
|
||||
#, python-brace-format
|
||||
msgid "{name} is asking for access to the document: {title}"
|
||||
msgstr "{name} 正要求存取文件:{title}"
|
||||
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
#: build/lib/core/models.py:1837 core/models.py:1837
|
||||
msgid "Thread"
|
||||
msgstr "對話串"
|
||||
|
||||
#: build/lib/core/models.py:1839 core/models.py:1839
|
||||
#: build/lib/core/models.py:1838 core/models.py:1838
|
||||
msgid "Threads"
|
||||
msgstr "對話串"
|
||||
|
||||
#: build/lib/core/models.py:1842 build/lib/core/models.py:1894
|
||||
#: core/models.py:1842 core/models.py:1894
|
||||
#: build/lib/core/models.py:1841 build/lib/core/models.py:1893
|
||||
#: core/models.py:1841 core/models.py:1893
|
||||
msgid "Anonymous"
|
||||
msgstr "匿名"
|
||||
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
#: build/lib/core/models.py:1888 core/models.py:1888
|
||||
msgid "Comment"
|
||||
msgstr "評論"
|
||||
|
||||
#: build/lib/core/models.py:1890 core/models.py:1890
|
||||
#: build/lib/core/models.py:1889 core/models.py:1889
|
||||
msgid "Comments"
|
||||
msgstr "評論"
|
||||
|
||||
#: build/lib/core/models.py:1939 core/models.py:1939
|
||||
#: build/lib/core/models.py:1938 core/models.py:1938
|
||||
msgid "This emoji has already been reacted to this comment."
|
||||
msgstr "此評論已標記過此表情符號。"
|
||||
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
#: build/lib/core/models.py:1942 core/models.py:1942
|
||||
msgid "Reaction"
|
||||
msgstr "回應"
|
||||
|
||||
#: build/lib/core/models.py:1944 core/models.py:1944
|
||||
#: build/lib/core/models.py:1943 core/models.py:1943
|
||||
msgid "Reactions"
|
||||
msgstr "回應"
|
||||
|
||||
#: build/lib/core/models.py:1954 core/models.py:1954
|
||||
#: build/lib/core/models.py:1953 core/models.py:1953
|
||||
msgid "email address"
|
||||
msgstr "電子郵件地址"
|
||||
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
#: build/lib/core/models.py:1972 core/models.py:1972
|
||||
msgid "Document invitation"
|
||||
msgstr "文件邀請"
|
||||
|
||||
#: build/lib/core/models.py:1974 core/models.py:1974
|
||||
#: build/lib/core/models.py:1973 core/models.py:1973
|
||||
msgid "Document invitations"
|
||||
msgstr "文件邀請"
|
||||
|
||||
#: build/lib/core/models.py:1994 core/models.py:1994
|
||||
#: build/lib/core/models.py:1993 core/models.py:1993
|
||||
msgid "This email is already associated to a registered user."
|
||||
msgstr "此電子郵件地址已與已註冊使用者關聯。"
|
||||
|
||||
#: build/lib/impress/settings.py:702 impress/settings.py:702
|
||||
#: build/lib/impress/settings.py:808 impress/settings.py:808
|
||||
msgid "Docs AI"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "impress"
|
||||
version = "4.8.2"
|
||||
version = "4.8.4"
|
||||
authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }]
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createDoc,
|
||||
getMenuItem,
|
||||
mockedDocument,
|
||||
overrideConfig,
|
||||
verifyDocName,
|
||||
@@ -47,9 +46,9 @@ test.describe('Doc AI feature', () => {
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('Anything');
|
||||
await page.getByText('Anything').selectText();
|
||||
expect(
|
||||
await page.locator('button[data-test="convertMarkdown"]').count(),
|
||||
).toBe(1);
|
||||
await expect(
|
||||
page.locator('button[data-test="convertMarkdown"]'),
|
||||
).toHaveCount(1);
|
||||
await expect(
|
||||
page.getByRole('button', { name: config.selector, exact: true }),
|
||||
).toBeHidden();
|
||||
@@ -179,18 +178,32 @@ test.describe('Doc AI feature', () => {
|
||||
|
||||
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
||||
|
||||
await expect(getMenuItem(page, 'Use as prompt')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Rephrase')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Summarize')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Correct')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Language')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Rephrase' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Summarize' }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeVisible();
|
||||
|
||||
await getMenuItem(page, 'Language').hover();
|
||||
await expect(getMenuItem(page, 'English', { exact: true })).toBeVisible();
|
||||
await expect(getMenuItem(page, 'French', { exact: true })).toBeVisible();
|
||||
await expect(getMenuItem(page, 'German', { exact: true })).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'Language' }).hover();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'English', exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'French', exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'German', exact: true }),
|
||||
).toBeVisible();
|
||||
|
||||
await getMenuItem(page, 'German', { exact: true }).click();
|
||||
await page.getByRole('menuitem', { name: 'German', exact: true }).click();
|
||||
|
||||
await expect(editor.getByText('Hallo Welt')).toBeVisible();
|
||||
});
|
||||
@@ -256,15 +269,23 @@ test.describe('Doc AI feature', () => {
|
||||
await page.getByRole('button', { name: 'AI', exact: true }).click();
|
||||
|
||||
if (ai_transform) {
|
||||
await expect(getMenuItem(page, 'Use as prompt')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(getMenuItem(page, 'Use as prompt')).toBeHidden();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Use as prompt' }),
|
||||
).toBeHidden();
|
||||
}
|
||||
|
||||
if (ai_translate) {
|
||||
await expect(getMenuItem(page, 'Language')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeVisible();
|
||||
} else {
|
||||
await expect(getMenuItem(page, 'Language')).toBeHidden();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Language' }),
|
||||
).toBeHidden();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
closeHeaderMenu,
|
||||
createDoc,
|
||||
getMenuItem,
|
||||
getOtherBrowserName,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
@@ -131,12 +130,13 @@ test.describe('Doc Comments', () => {
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
await expect(thread.getByText('This is a comment').first()).toBeHidden();
|
||||
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
|
||||
|
||||
// Check background color changed
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
await editor.first().click();
|
||||
await editor.getByText('Hello').click();
|
||||
|
||||
@@ -151,7 +151,7 @@ test.describe('Doc Comments', () => {
|
||||
// Edit Comment
|
||||
await thread.getByText('This is a comment').first().hover();
|
||||
await thread.locator('[data-test="moreactions"]').first().click();
|
||||
await getMenuItem(thread, 'Edit comment').click();
|
||||
await thread.getByRole('menuitem', { name: 'Edit comment' }).click();
|
||||
const commentEditor = thread.getByText('This is a comment').first();
|
||||
await commentEditor.fill('This is an edited comment');
|
||||
const saveBtn = thread.locator('button[data-test="save"]').first();
|
||||
@@ -176,7 +176,7 @@ test.describe('Doc Comments', () => {
|
||||
// Delete second comment
|
||||
await thread.getByText('This is a second comment').first().hover();
|
||||
await thread.locator('[data-test="moreactions"]').first().click();
|
||||
await getMenuItem(thread, 'Delete comment').click();
|
||||
await thread.getByRole('menuitem', { name: 'Delete comment' }).click();
|
||||
await expect(
|
||||
thread.getByText('This is a second comment').first(),
|
||||
).toBeHidden();
|
||||
@@ -185,6 +185,7 @@ test.describe('Doc Comments', () => {
|
||||
await thread.getByText('This is an edited comment').first().hover();
|
||||
await thread.locator('[data-test="resolve"]').click();
|
||||
await expect(thread).toBeHidden();
|
||||
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
@@ -196,18 +197,21 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await thread.getByRole('paragraph').first().fill('This is a new comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
await expect(editor.getByText('Hello')).toHaveClass('bn-thread-mark');
|
||||
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
await editor.first().click();
|
||||
await editor.getByText('Hello').click();
|
||||
|
||||
await thread.getByText('This is a new comment').first().hover();
|
||||
await thread.locator('[data-test="moreactions"]').first().click();
|
||||
await getMenuItem(thread, 'Delete comment').click();
|
||||
await thread.getByRole('menuitem', { name: 'Delete comment' }).click();
|
||||
|
||||
await expect(editor.getByText('Hello')).not.toHaveClass('bn-thread-mark');
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
@@ -263,7 +267,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
// We change the role of the second user to reader
|
||||
@@ -298,7 +302,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
await otherEditor.getByText('Hello').click();
|
||||
await expect(
|
||||
@@ -344,7 +348,7 @@ test.describe('Doc Comments', () => {
|
||||
|
||||
await expect(editor1.getByText('Document One')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
'color(srgb 0.882353 0.831373 0.717647 / 0.4)',
|
||||
);
|
||||
|
||||
await editor1.getByText('Document One').click();
|
||||
|
||||
@@ -5,7 +5,6 @@ import cs from 'convert-stream';
|
||||
|
||||
import {
|
||||
createDoc,
|
||||
getMenuItem,
|
||||
goToGridDoc,
|
||||
overrideConfig,
|
||||
verifyDocName,
|
||||
@@ -148,20 +147,18 @@ test.describe('Doc Editor', () => {
|
||||
const wsClosePromise = webSocket.waitForEvent('close');
|
||||
|
||||
await selectVisibility.click();
|
||||
await getMenuItem(page, 'Connected').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Connected' }).click();
|
||||
|
||||
// Assert that the doc reconnects to the ws
|
||||
const wsClose = await wsClosePromise;
|
||||
expect(wsClose.isClosed()).toBeTruthy();
|
||||
|
||||
// Check the ws is connected again
|
||||
webSocketPromise = page.waitForEvent('websocket', (webSocket) => {
|
||||
webSocket = await page.waitForEvent('websocket', (webSocket) => {
|
||||
return webSocket
|
||||
.url()
|
||||
.includes('ws://localhost:4444/collaboration/ws/?room=');
|
||||
});
|
||||
|
||||
webSocket = await webSocketPromise;
|
||||
framesentPromise = webSocket.waitForEvent('framesent');
|
||||
framesent = await framesentPromise;
|
||||
expect(framesent.payload).not.toBeNull();
|
||||
@@ -578,12 +575,10 @@ test.describe('Doc Editor', () => {
|
||||
|
||||
await page.reload();
|
||||
|
||||
responseCanEditPromise = page.waitForResponse(
|
||||
responseCanEdit = await page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().includes(`/can-edit/`) && response.status() === 200,
|
||||
);
|
||||
|
||||
responseCanEdit = await responseCanEditPromise;
|
||||
expect(responseCanEdit.ok()).toBeTruthy();
|
||||
|
||||
jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
|
||||
@@ -609,7 +604,7 @@ test.describe('Doc Editor', () => {
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
await page.getByTestId('doc-access-mode').click();
|
||||
await getMenuItem(page, 'Reading').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Reading' }).click();
|
||||
|
||||
// Close the modal
|
||||
await page.getByRole('button', { name: 'close' }).first().click();
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
createDoc,
|
||||
getGridRow,
|
||||
getMenuItem,
|
||||
getOtherBrowserName,
|
||||
mockedListDocs,
|
||||
toggleHeaderMenu,
|
||||
@@ -207,7 +206,7 @@ test.describe('Doc grid move', () => {
|
||||
const row = await getGridRow(page, titleDoc1);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await getMenuItem(page, 'Move into a doc').click();
|
||||
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
|
||||
@@ -295,7 +294,7 @@ test.describe('Doc grid move', () => {
|
||||
const row = await getGridRow(page, titleDoc1);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await getMenuItem(page, 'Move into a doc').click();
|
||||
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
|
||||
@@ -342,7 +341,9 @@ test.describe('Doc grid move', () => {
|
||||
`doc-share-access-request-row-${emailRequest}`,
|
||||
);
|
||||
await container.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(otherPage, 'Administrator').click();
|
||||
await otherPage
|
||||
.getByRole('menuitemradio', { name: 'Administrator' })
|
||||
.click();
|
||||
await container.getByRole('button', { name: 'Approve' }).click();
|
||||
|
||||
await expect(otherPage.getByText('Access Requests')).toBeHidden();
|
||||
@@ -353,7 +354,7 @@ test.describe('Doc grid move', () => {
|
||||
await page.reload();
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await getMenuItem(page, 'Move into a doc').click();
|
||||
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createDoc,
|
||||
getGridRow,
|
||||
getMenuItem,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
import { createDoc, getGridRow, verifyDocName } from './utils-common';
|
||||
import { addNewMember, connectOtherUserToDoc } from './utils-share';
|
||||
|
||||
type SmallDoc = {
|
||||
@@ -104,7 +99,7 @@ test.describe('Document grid item options', () => {
|
||||
const row = await getGridRow(page, docTitle);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await getMenuItem(page, 'Share').click();
|
||||
await page.getByRole('menuitem', { name: 'Share' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('dialog').getByText('Share the document'),
|
||||
@@ -120,7 +115,7 @@ test.describe('Document grid item options', () => {
|
||||
|
||||
// Pin
|
||||
await row.getByText(`more_horiz`).click();
|
||||
await getMenuItem(page, 'Pin').click();
|
||||
await page.getByRole('menuitem', { name: 'Pin' }).click();
|
||||
|
||||
// Check is pinned
|
||||
await expect(row.getByTestId('doc-pinned-icon')).toBeVisible();
|
||||
@@ -147,7 +142,7 @@ test.describe('Document grid item options', () => {
|
||||
const row = await getGridRow(page, docTitle);
|
||||
await row.getByText(`more_horiz`).click();
|
||||
|
||||
await getMenuItem(page, 'Delete').click();
|
||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Delete a doc' }),
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
createDoc,
|
||||
getGridRow,
|
||||
getMenuItem,
|
||||
goToGridDoc,
|
||||
mockedDocument,
|
||||
verifyDocName,
|
||||
@@ -79,7 +78,7 @@ test.describe('Doc Header', () => {
|
||||
|
||||
await page.getByTestId('doc-visibility').click();
|
||||
|
||||
await getMenuItem(page, 'Public').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Public' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).first().click();
|
||||
|
||||
@@ -153,8 +152,10 @@ test.describe('Doc Header', () => {
|
||||
|
||||
const emojiPicker = page.locator('.--docs--doc-title').getByRole('button');
|
||||
const optionMenu = page.getByLabel('Open the document options');
|
||||
const addEmojiMenuItem = getMenuItem(page, 'Add emoji');
|
||||
const removeEmojiMenuItem = getMenuItem(page, 'Remove emoji');
|
||||
const addEmojiMenuItem = page.getByRole('menuitem', { name: 'Add emoji' });
|
||||
const removeEmojiMenuItem = page.getByRole('menuitem', {
|
||||
name: 'Remove emoji',
|
||||
});
|
||||
|
||||
// Top parent should not have emoji picker
|
||||
await expect(emojiPicker).toBeHidden();
|
||||
@@ -208,7 +209,7 @@ test.describe('Doc Header', () => {
|
||||
const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1);
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await getMenuItem(page, 'Delete document').click();
|
||||
await page.getByRole('menuitem', { name: 'Delete document' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Delete a doc' }),
|
||||
@@ -236,7 +237,7 @@ test.describe('Doc Header', () => {
|
||||
hasText: randomDoc,
|
||||
});
|
||||
|
||||
expect(await row.count()).toBe(0);
|
||||
await expect(row).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('it checks the options available if administrator', async ({ page }) => {
|
||||
@@ -270,10 +271,12 @@ test.describe('Doc Header', () => {
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await expect(getMenuItem(page, 'Delete document')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Delete document' }),
|
||||
).toBeDisabled();
|
||||
|
||||
// Click somewhere else to close the options
|
||||
await page.click('body', { position: { x: 0, y: 0 } });
|
||||
await page.locator('body').click({ position: { x: 0, y: 0 } });
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
@@ -293,7 +296,7 @@ test.describe('Doc Header', () => {
|
||||
|
||||
await invitationRole.click();
|
||||
|
||||
await getMenuItem(page, 'Remove access').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Remove access' }).click();
|
||||
await expect(invitationCard).toBeHidden();
|
||||
|
||||
const memberCard = shareModal.getByLabel('List members card');
|
||||
@@ -305,7 +308,9 @@ test.describe('Doc Header', () => {
|
||||
await expect(roles).toBeVisible();
|
||||
|
||||
await roles.click();
|
||||
await expect(getMenuItem(page, 'Remove access')).toBeEnabled();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Remove access' }),
|
||||
).toBeEnabled();
|
||||
});
|
||||
|
||||
test('it checks the options available if editor', async ({ page }) => {
|
||||
@@ -345,10 +350,12 @@ test.describe('Doc Header', () => {
|
||||
).toBeVisible();
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await expect(getMenuItem(page, 'Delete document')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Delete document' }),
|
||||
).toBeDisabled();
|
||||
|
||||
// Click somewhere else to close the options
|
||||
await page.click('body', { position: { x: 0, y: 0 } });
|
||||
await page.locator('body').click({ position: { x: 0, y: 0 } });
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
@@ -415,10 +422,12 @@ test.describe('Doc Header', () => {
|
||||
).toBeVisible();
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await expect(getMenuItem(page, 'Delete document')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Delete document' }),
|
||||
).toBeDisabled();
|
||||
|
||||
// Click somewhere else to close the options
|
||||
await page.click('body', { position: { x: 0, y: 0 } });
|
||||
await page.locator('body').click({ position: { x: 0, y: 0 } });
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
@@ -473,8 +482,10 @@ test.describe('Doc Header', () => {
|
||||
|
||||
// Copy content to clipboard
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await getMenuItem(page, 'Copy as Markdown').click();
|
||||
await expect(page.getByText('Copied to clipboard')).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'Copy as Markdown' }).click();
|
||||
await expect(
|
||||
page.getByText('Copied as Markdown to clipboard'),
|
||||
).toBeVisible();
|
||||
|
||||
// Test that clipboard is in Markdown format
|
||||
const handle = await page.evaluateHandle(() =>
|
||||
@@ -535,7 +546,7 @@ test.describe('Doc Header', () => {
|
||||
.click();
|
||||
|
||||
// Pin
|
||||
await getMenuItem(page, 'Pin').click();
|
||||
await page.getByRole('menuitem', { name: 'Pin' }).click();
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the document options' })
|
||||
.click();
|
||||
@@ -556,11 +567,11 @@ test.describe('Doc Header', () => {
|
||||
.click();
|
||||
|
||||
// Unpin
|
||||
await getMenuItem(page, 'Unpin').click();
|
||||
await page.getByRole('menuitem', { name: 'Unpin' }).click();
|
||||
await page
|
||||
.getByRole('button', { name: 'Open the document options' })
|
||||
.click();
|
||||
await expect(getMenuItem(page, 'Pin')).toBeVisible();
|
||||
await expect(page.getByRole('menuitem', { name: 'Pin' })).toBeVisible();
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
@@ -578,7 +589,7 @@ test.describe('Doc Header', () => {
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
|
||||
await getMenuItem(page, 'Duplicate').click();
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
await expect(
|
||||
page.getByText('Document duplicated successfully!'),
|
||||
).toBeVisible();
|
||||
@@ -593,7 +604,7 @@ test.describe('Doc Header', () => {
|
||||
await expect(row.getByText(duplicateTitle)).toBeVisible();
|
||||
|
||||
await row.getByText(`more_horiz`).click();
|
||||
await getMenuItem(page, 'Duplicate').click();
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
const duplicateDuplicateTitle = 'Copy of ' + duplicateTitle;
|
||||
await page.getByText(duplicateDuplicateTitle).click();
|
||||
await expect(page.getByText('Hello Duplicated World')).toBeVisible();
|
||||
@@ -626,7 +637,7 @@ test.describe('Doc Header', () => {
|
||||
|
||||
const currentUrl = page.url();
|
||||
|
||||
await getMenuItem(page, 'Duplicate').click();
|
||||
await page.getByRole('menuitem', { name: 'Duplicate' }).click();
|
||||
|
||||
await expect(page).not.toHaveURL(new RegExp(currentUrl));
|
||||
|
||||
@@ -665,8 +676,10 @@ test.describe('Documents Header mobile', () => {
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Copy link' })).toBeHidden();
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await expect(getMenuItem(page, 'Copy link')).toBeVisible();
|
||||
await getMenuItem(page, 'Share').click();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Copy link' }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('menuitem', { name: 'Share' }).click();
|
||||
await expect(page.getByRole('button', { name: 'Copy link' })).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -689,7 +702,7 @@ test.describe('Documents Header mobile', () => {
|
||||
await goToGridDoc(page);
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await getMenuItem(page, 'Share').click();
|
||||
await page.getByRole('menuitem', { name: 'Share' }).click();
|
||||
|
||||
const shareModal = page.getByRole('dialog', {
|
||||
name: 'Share the document',
|
||||
|
||||
@@ -177,5 +177,5 @@ const dragAndDropFiles = async (
|
||||
return dt;
|
||||
}, filesData);
|
||||
|
||||
await page.dispatchEvent(selector, 'drop', { dataTransfer });
|
||||
await page.locator(selector).dispatchEvent('drop', { dataTransfer });
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, getMenuItem, verifyDocName } from './utils-common';
|
||||
import { createDoc, verifyDocName } from './utils-common';
|
||||
import { updateShareLink } from './utils-share';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
@@ -53,17 +53,19 @@ test.describe('Inherited share accesses', () => {
|
||||
await expect(docVisibilityCard.getByText('Reading')).toBeVisible();
|
||||
|
||||
await docVisibilityCard.getByText('Reading').click();
|
||||
await getMenuItem(page, 'Editing').click();
|
||||
await page.getByRole('menuitemradio', { 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(getMenuItem(page, 'Private')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Private' }),
|
||||
).toBeDisabled();
|
||||
|
||||
// Update child link
|
||||
await getMenuItem(page, 'Public').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Public' }).click();
|
||||
|
||||
await expect(docVisibilityCard.getByText('Connected')).toBeHidden();
|
||||
await expect(
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
BROWSERS,
|
||||
createDoc,
|
||||
getMenuItem,
|
||||
keyCloakSignIn,
|
||||
randomName,
|
||||
verifyDocName,
|
||||
@@ -17,6 +16,41 @@ test.describe('Document create member', () => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('it checks search hints', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'select-multi-users', browserName, 1);
|
||||
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
|
||||
const shareModal = page.getByLabel('Share the document');
|
||||
await expect(shareModal.getByText('Document owner')).toBeVisible();
|
||||
|
||||
const inputSearch = page.getByTestId('quick-search-input');
|
||||
await inputSearch.fill('u');
|
||||
await expect(shareModal.getByText('Document owner')).toBeHidden();
|
||||
await expect(
|
||||
shareModal.getByText('Type at least 3 characters to display user names'),
|
||||
).toBeVisible();
|
||||
await inputSearch.fill('user');
|
||||
await expect(
|
||||
shareModal.getByText('Type at least 3 characters to display user names'),
|
||||
).toBeHidden();
|
||||
await expect(shareModal.getByText('Choose a user')).toBeVisible();
|
||||
await inputSearch.fill('anything');
|
||||
await expect(shareModal.getByText('Choose a user')).toBeHidden();
|
||||
await expect(
|
||||
shareModal.getByText(
|
||||
'No results. Type a full email address to invite someone.',
|
||||
),
|
||||
).toBeVisible();
|
||||
await inputSearch.fill('anything@test.com');
|
||||
await expect(
|
||||
shareModal.getByText(
|
||||
'No results. Type a full email address to invite someone.',
|
||||
),
|
||||
).toBeHidden();
|
||||
await expect(shareModal.getByText('Choose the email')).toBeVisible();
|
||||
});
|
||||
|
||||
test('it selects 2 users and 1 invitation', async ({ page, browserName }) => {
|
||||
const inputFill = 'user.test';
|
||||
const responsePromise = page.waitForResponse(
|
||||
@@ -76,13 +110,21 @@ test.describe('Document create member', () => {
|
||||
|
||||
// Check roles are displayed
|
||||
await list.getByTestId('doc-role-dropdown').click();
|
||||
await expect(getMenuItem(page, 'Reader')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Editor')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Owner')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Administrator')).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Reader' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Editor' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Owner' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Administrator' }),
|
||||
).toBeVisible();
|
||||
|
||||
// Validate
|
||||
await getMenuItem(page, 'Administrator').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Administrator' }).click();
|
||||
await page.getByTestId('doc-share-invite-button').click();
|
||||
|
||||
// Check invitation added
|
||||
@@ -128,7 +170,7 @@ test.describe('Document create member', () => {
|
||||
// Choose a role
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, 'Owner').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Owner' }).click();
|
||||
|
||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||
(response) =>
|
||||
@@ -146,7 +188,7 @@ test.describe('Document create member', () => {
|
||||
|
||||
// Choose a role
|
||||
await container.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, 'Owner').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Owner' }).click();
|
||||
|
||||
const responsePromiseCreateInvitationFail = page.waitForResponse(
|
||||
(response) =>
|
||||
@@ -183,7 +225,7 @@ test.describe('Document create member', () => {
|
||||
// Choose a role
|
||||
const container = page.getByTestId('doc-share-add-member-list');
|
||||
await container.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, 'Administrator').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Administrator' }).click();
|
||||
|
||||
const responsePromiseCreateInvitation = page.waitForResponse(
|
||||
(response) =>
|
||||
@@ -210,13 +252,13 @@ test.describe('Document create member', () => {
|
||||
);
|
||||
|
||||
await userInvitation.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, 'Reader').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Reader' }).click();
|
||||
|
||||
const responsePatchInvitation = await responsePromisePatchInvitation;
|
||||
expect(responsePatchInvitation.ok()).toBeTruthy();
|
||||
|
||||
await userInvitation.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, 'Remove access').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Remove access' }).click();
|
||||
|
||||
await expect(userInvitation).toBeHidden();
|
||||
});
|
||||
@@ -268,7 +310,7 @@ test.describe('Document create member', () => {
|
||||
`doc-share-access-request-row-${emailRequest}`,
|
||||
);
|
||||
await container.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, 'Administrator').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Administrator' }).click();
|
||||
await container.getByRole('button', { name: 'Approve' }).click();
|
||||
|
||||
await expect(page.getByText('Access Requests')).toBeHidden();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, getMenuItem, verifyDocName } from './utils-common';
|
||||
import { createDoc, verifyDocName } from './utils-common';
|
||||
import { addNewMember } from './utils-share';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -160,7 +160,9 @@ test.describe('Document list members', () => {
|
||||
`You are the sole owner of this group, make another member the group owner before you can change your own role or be removed from your document.`,
|
||||
);
|
||||
await expect(soloOwner).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Administrator')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Administrator' }),
|
||||
).toBeDisabled();
|
||||
|
||||
await list.click({
|
||||
force: true, // Force click to close the dropdown
|
||||
@@ -183,18 +185,20 @@ test.describe('Document list members', () => {
|
||||
});
|
||||
|
||||
await currentUserRole.click();
|
||||
await getMenuItem(page, 'Administrator').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Administrator' }).click();
|
||||
await list.click();
|
||||
await expect(currentUserRole).toBeVisible();
|
||||
|
||||
await newUserRoles.click();
|
||||
await expect(getMenuItem(page, 'Owner')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Owner' }),
|
||||
).toBeDisabled();
|
||||
await list.click({
|
||||
force: true, // Force click to close the dropdown
|
||||
});
|
||||
|
||||
await currentUserRole.click();
|
||||
await getMenuItem(page, 'Reader').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Reader' }).click();
|
||||
await list.click({
|
||||
force: true, // Force click to close the dropdown
|
||||
});
|
||||
@@ -234,11 +238,11 @@ test.describe('Document list members', () => {
|
||||
await expect(userReader).toBeVisible();
|
||||
|
||||
await userReaderRole.click();
|
||||
await getMenuItem(page, 'Remove access').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Remove access' }).click();
|
||||
await expect(userReader).toBeHidden();
|
||||
|
||||
await mySelfRole.click();
|
||||
await getMenuItem(page, 'Remove access').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Remove access' }).click();
|
||||
await expect(
|
||||
page.getByText('Insufficient access rights to view the document.'),
|
||||
).toBeVisible();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, getMenuItem, verifyDocName } from './utils-common';
|
||||
import { createDoc, verifyDocName } from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -29,7 +29,7 @@ test.describe('Document search', () => {
|
||||
await page.getByTestId('search-docs-button').click();
|
||||
|
||||
await expect(
|
||||
page.getByRole('img', { name: 'No active search' }),
|
||||
page.getByLabel('Search modal').locator('img[alt=""]'),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
@@ -107,7 +107,7 @@ test.describe('Document search', () => {
|
||||
|
||||
await searchButton.click();
|
||||
await expect(
|
||||
page.getByRole('combobox', { name: 'Quick search input' }),
|
||||
page.getByRole('combobox', { name: 'Search documents' }),
|
||||
).toBeVisible();
|
||||
await expect(filters).toBeHidden();
|
||||
|
||||
@@ -120,7 +120,7 @@ test.describe('Document search', () => {
|
||||
|
||||
await searchButton.click();
|
||||
await expect(
|
||||
page.getByRole('combobox', { name: 'Quick search input' }),
|
||||
page.getByRole('combobox', { name: 'Search documents' }),
|
||||
).toBeVisible();
|
||||
await expect(filters).toBeHidden();
|
||||
|
||||
@@ -136,9 +136,13 @@ test.describe('Document search', () => {
|
||||
|
||||
await filters.click();
|
||||
await filters.getByRole('button', { name: 'Current doc' }).click();
|
||||
await expect(getMenuItem(page, 'All docs')).toBeVisible();
|
||||
await expect(getMenuItem(page, 'Current doc')).toBeVisible();
|
||||
await getMenuItem(page, 'All docs').click();
|
||||
await expect(
|
||||
page.getByRole('menuitemcheckbox', { name: 'All docs' }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole('menuitemcheckbox', { name: 'Current doc' }),
|
||||
).toBeVisible();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'All docs' }).click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible();
|
||||
});
|
||||
@@ -164,9 +168,9 @@ test.describe('Document search', () => {
|
||||
const searchButton = page.getByTestId('search-docs-button');
|
||||
|
||||
await searchButton.click();
|
||||
await page.getByRole('combobox', { name: 'Quick search input' }).click();
|
||||
await page.getByRole('combobox', { name: 'Search documents' }).click();
|
||||
await page
|
||||
.getByRole('combobox', { name: 'Quick search input' })
|
||||
.getByRole('combobox', { name: 'Search documents' })
|
||||
.fill('sub page search');
|
||||
|
||||
// Expect to find the first and second docs in the results list
|
||||
@@ -188,7 +192,7 @@ test.describe('Document search', () => {
|
||||
);
|
||||
await searchButton.click();
|
||||
await page
|
||||
.getByRole('combobox', { name: 'Quick search input' })
|
||||
.getByRole('combobox', { name: 'Search documents' })
|
||||
.fill('second');
|
||||
|
||||
// Now there is a sub page - expect to have the focus on the current doc
|
||||
|
||||
@@ -19,7 +19,9 @@ test.describe('Doc Table Content', () => {
|
||||
|
||||
await page.locator('.ProseMirror').click();
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Summary' })).toBeHidden();
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Show the table of contents' }),
|
||||
).toBeHidden();
|
||||
|
||||
await page.keyboard.type('# Level 1\n## Level 2\n### Level 3');
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { expect, test } from '@playwright/test';
|
||||
import {
|
||||
createDoc,
|
||||
expectLoginPage,
|
||||
getMenuItem,
|
||||
keyCloakSignIn,
|
||||
updateDocTitle,
|
||||
verifyDocName,
|
||||
@@ -43,15 +42,12 @@ test.describe('Doc Tree', () => {
|
||||
await expect(secondSubPageItem).toBeVisible();
|
||||
|
||||
// Check the position of the sub pages
|
||||
const allSubPageItems = await docTree
|
||||
.getByTestId(/^doc-sub-page-item/)
|
||||
.all();
|
||||
|
||||
expect(allSubPageItems.length).toBe(2);
|
||||
const allSubPageItems = docTree.getByTestId(/^doc-sub-page-item/);
|
||||
await expect(allSubPageItems).toHaveCount(2);
|
||||
|
||||
// Check that elements are in the correct order
|
||||
await expect(allSubPageItems[0].getByText('first move')).toBeVisible();
|
||||
await expect(allSubPageItems[1].getByText('second move')).toBeVisible();
|
||||
await expect(allSubPageItems.nth(0).getByText('first move')).toBeVisible();
|
||||
await expect(allSubPageItems.nth(1).getByText('second move')).toBeVisible();
|
||||
|
||||
// Will move the first sub page to the second position
|
||||
const firstSubPageBoundingBox = await firstSubPageItem.boundingBox();
|
||||
@@ -91,17 +87,15 @@ test.describe('Doc Tree', () => {
|
||||
await expect(secondSubPageItem).toBeVisible();
|
||||
|
||||
// Check that elements are in the correct order
|
||||
const allSubPageItemsAfterReload = await docTree
|
||||
.getByTestId(/^doc-sub-page-item/)
|
||||
.all();
|
||||
|
||||
expect(allSubPageItemsAfterReload.length).toBe(2);
|
||||
const allSubPageItemsAfterReload =
|
||||
docTree.getByTestId(/^doc-sub-page-item/);
|
||||
await expect(allSubPageItemsAfterReload).toHaveCount(2);
|
||||
|
||||
await expect(
|
||||
allSubPageItemsAfterReload[0].getByText('second move'),
|
||||
allSubPageItemsAfterReload.nth(0).getByText('second move'),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
allSubPageItemsAfterReload[1].getByText('first move'),
|
||||
allSubPageItemsAfterReload.nth(1).getByText('first move'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -163,7 +157,7 @@ test.describe('Doc Tree', () => {
|
||||
);
|
||||
const currentUserRole = currentUser.getByTestId('doc-role-dropdown');
|
||||
await currentUserRole.click();
|
||||
await getMenuItem(page, 'Administrator').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Administrator' }).click();
|
||||
await list.click();
|
||||
|
||||
await page.getByRole('button', { name: 'Ok' }).click();
|
||||
@@ -193,10 +187,9 @@ test.describe('Doc Tree', () => {
|
||||
const menu = child.getByText(`more_horiz`);
|
||||
await menu.click();
|
||||
|
||||
await expect(getMenuItem(page, 'Move to my docs')).toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Move to my docs' }),
|
||||
).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
test('keyboard navigation with Enter key opens documents', async ({
|
||||
@@ -299,7 +292,7 @@ test.describe('Doc Tree', () => {
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
await expect(page.getByLabel('Open onboarding menu')).toBeFocused();
|
||||
await expect(page.getByLabel('Open help menu')).toBeFocused();
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
@@ -309,7 +302,7 @@ test.describe('Doc Tree', () => {
|
||||
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
|
||||
await expect(page.getByLabel('Open onboarding menu')).toBeFocused();
|
||||
await expect(page.getByLabel('Open help menu')).toBeFocused();
|
||||
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
|
||||
@@ -340,7 +333,9 @@ test.describe('Doc Tree', () => {
|
||||
await row.hover();
|
||||
const menu = row.getByText(`more_horiz`);
|
||||
await menu.click();
|
||||
await expect(getMenuItem(page, 'Remove emoji')).toBeHidden();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Remove emoji' }),
|
||||
).toBeHidden();
|
||||
|
||||
// Close the menu
|
||||
await page.keyboard.press('Escape');
|
||||
@@ -360,7 +355,7 @@ test.describe('Doc Tree', () => {
|
||||
// Now remove the emoji using the new action
|
||||
await row.hover();
|
||||
await menu.click();
|
||||
await getMenuItem(page, 'Remove emoji').click();
|
||||
await page.getByRole('menuitem', { name: 'Remove emoji' }).click();
|
||||
|
||||
await expect(row.getByText('😀')).toBeHidden();
|
||||
await expect(titleEmojiPicker).toBeHidden();
|
||||
@@ -390,7 +385,7 @@ test.describe('Doc Tree: Inheritance', () => {
|
||||
const selectVisibility = page.getByTestId('doc-visibility');
|
||||
await selectVisibility.click();
|
||||
|
||||
await getMenuItem(page, 'Public').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Public' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
|
||||
@@ -2,7 +2,6 @@ import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createDoc,
|
||||
getMenuItem,
|
||||
goToGridDoc,
|
||||
mockedDocument,
|
||||
verifyDocName,
|
||||
@@ -21,11 +20,11 @@ test.describe('Doc Version', () => {
|
||||
|
||||
// Initially, there is no version
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await getMenuItem(page, 'Version history').click();
|
||||
await page.getByRole('menuitem', { name: 'Version history' }).click();
|
||||
await expect(page.getByText('History', { exact: true })).toBeVisible();
|
||||
|
||||
const modal = page.getByRole('dialog', { name: 'Version history' });
|
||||
const panel = modal.getByLabel('version list');
|
||||
const panel = modal.getByLabel('Version list');
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(modal.getByText('No versions')).toBeVisible();
|
||||
|
||||
@@ -75,14 +74,14 @@ test.describe('Doc Version', () => {
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await getMenuItem(page, 'Version history').click();
|
||||
await page.getByRole('menuitem', { name: 'Version history' }).click();
|
||||
|
||||
await expect(panel).toBeVisible();
|
||||
await expect(page.getByText('History', { exact: true })).toBeVisible();
|
||||
await expect(page.getByRole('status')).toBeHidden();
|
||||
const items = await panel.locator('.version-item').all();
|
||||
expect(items.length).toBe(2);
|
||||
await items[1].click();
|
||||
const items = panel.locator('.version-item');
|
||||
await expect(items).toHaveCount(2);
|
||||
await items.nth(1).click();
|
||||
|
||||
await expect(modal.getByText('Hello World')).toBeVisible();
|
||||
await expect(modal.getByText('It will create a version')).toBeHidden();
|
||||
@@ -90,7 +89,7 @@ test.describe('Doc Version', () => {
|
||||
modal.locator('div[data-content-type="callout"]').first(),
|
||||
).toBeHidden();
|
||||
|
||||
await items[0].click();
|
||||
await items.nth(0).click();
|
||||
|
||||
await expect(modal.getByText('Hello World')).toBeVisible();
|
||||
await expect(modal.getByText('It will create a version')).toBeVisible();
|
||||
@@ -101,7 +100,7 @@ test.describe('Doc Version', () => {
|
||||
modal.getByText('It will create a second version'),
|
||||
).toBeHidden();
|
||||
|
||||
await items[1].click();
|
||||
await items.nth(1).click();
|
||||
|
||||
await expect(modal.getByText('Hello World')).toBeVisible();
|
||||
await expect(modal.getByText('It will create a version')).toBeHidden();
|
||||
@@ -125,7 +124,9 @@ test.describe('Doc Version', () => {
|
||||
await verifyDocName(page, 'Mocked document');
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await expect(getMenuItem(page, 'Version history')).toBeDisabled();
|
||||
await expect(
|
||||
page.getByRole('menuitem', { name: 'Version history' }),
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
test('it restores the doc version', async ({ page, browserName }) => {
|
||||
@@ -152,23 +153,28 @@ test.describe('Doc Version', () => {
|
||||
await expect(page.getByText('World')).toBeVisible();
|
||||
|
||||
await page.getByLabel('Open the document options').click();
|
||||
await getMenuItem(page, 'Version history').click();
|
||||
await page.getByRole('menuitem', { name: 'Version history' }).click();
|
||||
|
||||
const modal = page.getByRole('dialog', { name: 'Version history' });
|
||||
const panel = modal.getByLabel('version list');
|
||||
const panel = modal.getByLabel('Version list');
|
||||
await expect(panel).toBeVisible();
|
||||
|
||||
await expect(page.getByText('History', { exact: true })).toBeVisible();
|
||||
await panel.getByRole('button', { name: 'version item' }).click();
|
||||
await panel.locator('.version-item').first().click();
|
||||
|
||||
await expect(modal.getByText('World')).toBeHidden();
|
||||
|
||||
await page.getByRole('button', { name: 'Restore' }).click();
|
||||
await expect(page.getByText('Your current document will')).toBeVisible();
|
||||
await page.getByText('If a member is editing, his').click();
|
||||
await page.getByRole('button', { name: 'Restore', exact: true }).click();
|
||||
await expect(
|
||||
page.getByText(
|
||||
"The current document will be replaced, but you'll still find it in the version history.",
|
||||
),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByLabel('Restore', { exact: true }).click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.getByText('Hello')).toBeVisible();
|
||||
await expect(page.getByText('World')).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
BROWSERS,
|
||||
createDoc,
|
||||
expectLoginPage,
|
||||
getMenuItem,
|
||||
keyCloakSignIn,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
@@ -47,17 +46,21 @@ test.describe('Doc Visibility', () => {
|
||||
|
||||
await expect(selectVisibility.getByText('Private')).toBeVisible();
|
||||
|
||||
await expect(getMenuItem(page, 'Read only')).toBeHidden();
|
||||
await expect(getMenuItem(page, 'Can read and edit')).toBeHidden();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Read only' }),
|
||||
).toBeHidden();
|
||||
await expect(
|
||||
page.getByRole('menuitemradio', { name: 'Can read and edit' }),
|
||||
).toBeHidden();
|
||||
|
||||
await selectVisibility.click();
|
||||
await getMenuItem(page, 'Connected').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Connected' }).click();
|
||||
|
||||
await expect(page.getByTestId('doc-access-mode')).toBeVisible();
|
||||
|
||||
await selectVisibility.click();
|
||||
|
||||
await getMenuItem(page, 'Public').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Public' }).click();
|
||||
|
||||
await expect(page.getByTestId('doc-access-mode')).toBeVisible();
|
||||
});
|
||||
@@ -202,7 +205,7 @@ test.describe('Doc Visibility: Public', () => {
|
||||
const selectVisibility = page.getByTestId('doc-visibility');
|
||||
await selectVisibility.click();
|
||||
|
||||
await getMenuItem(page, 'Public').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Public' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
@@ -210,7 +213,7 @@ test.describe('Doc Visibility: Public', () => {
|
||||
|
||||
await expect(page.getByTestId('doc-access-mode')).toBeVisible();
|
||||
await page.getByTestId('doc-access-mode').click();
|
||||
await getMenuItem(page, 'Reading').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Reading' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.').first(),
|
||||
@@ -296,14 +299,14 @@ test.describe('Doc Visibility: Public', () => {
|
||||
const selectVisibility = page.getByTestId('doc-visibility');
|
||||
await selectVisibility.click();
|
||||
|
||||
await getMenuItem(page, 'Public').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Public' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId('doc-access-mode').click();
|
||||
await getMenuItem(page, 'Editing').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Editing' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.').first(),
|
||||
@@ -387,7 +390,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
const selectVisibility = page.getByTestId('doc-visibility');
|
||||
await selectVisibility.click();
|
||||
await getMenuItem(page, 'Connected').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Connected' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
@@ -435,7 +438,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
const selectVisibility = page.getByTestId('doc-visibility');
|
||||
await selectVisibility.click();
|
||||
await getMenuItem(page, 'Connected').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Connected' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
@@ -533,7 +536,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
const selectVisibility = page.getByTestId('doc-visibility');
|
||||
await selectVisibility.click();
|
||||
await getMenuItem(page, 'Connected').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Connected' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.'),
|
||||
@@ -541,7 +544,7 @@ test.describe('Doc Visibility: Authenticated', () => {
|
||||
|
||||
const urlDoc = page.url();
|
||||
await page.getByTestId('doc-access-mode').click();
|
||||
await getMenuItem(page, 'Editing').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Editing' }).click();
|
||||
|
||||
await expect(
|
||||
page.getByText('The document visibility has been updated.').first(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { getMenuItem, overrideConfig } from './utils-common';
|
||||
import { overrideConfig } from './utils-common';
|
||||
|
||||
test.describe('Footer', () => {
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
@@ -47,7 +47,7 @@ test.describe('Footer', () => {
|
||||
// Check the translation
|
||||
const header = page.locator('header').first();
|
||||
await header.getByRole('button').getByText('English').click();
|
||||
await getMenuItem(page, 'Français').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Français' }).click();
|
||||
|
||||
await expect(
|
||||
page.locator('footer').getByText('Mentions légales'),
|
||||
@@ -131,7 +131,7 @@ test.describe('Footer', () => {
|
||||
// Check the translation
|
||||
const header = page.locator('header').first();
|
||||
await header.getByRole('button').getByText('English').click();
|
||||
await getMenuItem(page, 'Français').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Français' }).click();
|
||||
|
||||
await expect(
|
||||
page
|
||||
|
||||
@@ -191,25 +191,27 @@ test.describe('Header: Override configuration', () => {
|
||||
});
|
||||
|
||||
test.describe('Header: Skip to Content', () => {
|
||||
test('it displays skip link on first TAB and focuses main content on click', async ({
|
||||
test('it displays skip link on first TAB and focuses page heading on click', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Wait for skip button to be mounted (client-side only component)
|
||||
const skipButton = page.getByRole('button', { name: 'Go to content' });
|
||||
await skipButton.waitFor({ state: 'attached' });
|
||||
// Wait for skip link to be mounted (client-side only component)
|
||||
const skipLink = page.getByRole('link', { name: 'Go to content' });
|
||||
await skipLink.waitFor({ state: 'attached' });
|
||||
|
||||
// First TAB shows the skip button
|
||||
// First TAB shows the skip link
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// The skip button should be visible and focused
|
||||
await expect(skipButton).toBeFocused();
|
||||
await expect(skipButton).toBeVisible();
|
||||
|
||||
// Clicking moves focus to the main content
|
||||
await skipButton.click();
|
||||
const mainContent = page.locator('main#mainContent');
|
||||
await expect(mainContent).toBeFocused();
|
||||
// The skip link should be visible and focused
|
||||
await expect(skipLink).toBeFocused();
|
||||
await expect(skipLink).toBeVisible();
|
||||
// Clicking moves focus to the page heading
|
||||
await skipLink.click();
|
||||
const pageHeading = page.getByRole('heading', {
|
||||
name: 'All docs',
|
||||
level: 2,
|
||||
});
|
||||
await expect(pageHeading).toBeFocused();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { expect, test } from '@playwright/test';
|
||||
|
||||
import {
|
||||
TestLanguage,
|
||||
getMenuItem,
|
||||
overrideConfig,
|
||||
waitForLanguageSwitch,
|
||||
} from './utils-common';
|
||||
@@ -27,7 +26,7 @@ test.describe('Help feature', () => {
|
||||
await expect(page.getByRole('button', { name: 'New doc' })).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Open onboarding menu' }),
|
||||
page.getByRole('button', { name: 'Open help menu' }),
|
||||
).toBeHidden();
|
||||
});
|
||||
|
||||
@@ -43,9 +42,9 @@ test.describe('Help feature', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await page.getByRole('button', { name: 'Open onboarding menu' }).click();
|
||||
await page.getByRole('button', { name: 'Open help menu' }).click();
|
||||
|
||||
await getMenuItem(page, 'Onboarding').click();
|
||||
await page.getByRole('menuitem', { name: 'Onboarding' }).click();
|
||||
|
||||
const modal = page.getByTestId('onboarding-modal');
|
||||
await expect(modal).toBeVisible();
|
||||
@@ -87,8 +86,8 @@ test.describe('Help feature', () => {
|
||||
});
|
||||
|
||||
test('closes modal with Skip button', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Open onboarding menu' }).click();
|
||||
await getMenuItem(page, 'Onboarding').click();
|
||||
await page.getByRole('button', { name: 'Open help menu' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Onboarding' }).click();
|
||||
|
||||
const modal = page.getByTestId('onboarding-modal');
|
||||
await expect(modal).toBeVisible();
|
||||
@@ -107,11 +106,9 @@ test.describe('Help feature', () => {
|
||||
// switch to french
|
||||
await waitForLanguageSwitch(page, TestLanguage.French);
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: "Ouvrir le menu d'embarquement" })
|
||||
.click();
|
||||
await page.getByRole('button', { name: "Ouvrir le menu d'aide" }).click();
|
||||
|
||||
await getMenuItem(page, 'Premiers pas').click();
|
||||
await page.getByRole('menuitem', { name: 'Premiers pas' }).click();
|
||||
|
||||
const modal = page.getByLabel('Apprenez les principes fondamentaux');
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ test.describe('Language', () => {
|
||||
|
||||
await expect(page.locator('[role="menu"]')).toBeVisible();
|
||||
|
||||
const menuItems = page.locator('[role="menuitem"], [role="menuitemradio"]');
|
||||
const menuItems = page.locator('[role="menuitemradio"]');
|
||||
await expect(menuItems.first()).toBeVisible();
|
||||
|
||||
await menuItems.first().click();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, goToGridDoc, verifyDocName } from './utils-common';
|
||||
import { createRootSubPage } from './utils-sub-pages';
|
||||
|
||||
test.describe('Left panel desktop', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
@@ -18,7 +19,7 @@ test.describe('Left panel desktop', () => {
|
||||
await expect(page.getByTestId('home-button')).toBeVisible();
|
||||
});
|
||||
|
||||
test('focuses main content after switching the docs filter', async ({
|
||||
test('focuses page heading after switching the docs filter', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto('/');
|
||||
@@ -28,8 +29,11 @@ test.describe('Left panel desktop', () => {
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(page).toHaveURL(/target=my_docs/);
|
||||
|
||||
const mainContent = page.locator('main#mainContent');
|
||||
await expect(mainContent).toBeFocused();
|
||||
const pageHeading = page.getByRole('heading', {
|
||||
name: 'My docs',
|
||||
level: 2,
|
||||
});
|
||||
await expect(pageHeading).toBeFocused();
|
||||
});
|
||||
|
||||
test('checks resize handle is present and functional on document page', async ({
|
||||
@@ -118,6 +122,47 @@ test.describe('Left panel mobile', () => {
|
||||
await expect(logoutButton).toBeInViewport();
|
||||
});
|
||||
|
||||
test('checks panel closes when clicking on a subdoc', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [docTitle] = await createDoc(
|
||||
page,
|
||||
'mobile-doc-test',
|
||||
browserName,
|
||||
1,
|
||||
true,
|
||||
);
|
||||
|
||||
const { name: docChild } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'mobile-doc-test-child',
|
||||
true,
|
||||
);
|
||||
|
||||
const { name: docChild2 } = await createRootSubPage(
|
||||
page,
|
||||
browserName,
|
||||
'mobile-doc-test-child-2',
|
||||
true,
|
||||
);
|
||||
|
||||
const header = page.locator('header').first();
|
||||
await header.getByLabel('Open the header menu').click();
|
||||
|
||||
await expect(page.getByTestId('left-panel-mobile')).toBeInViewport();
|
||||
|
||||
const docTree = page.getByTestId('doc-tree');
|
||||
await expect(docTree.getByText(docTitle)).toBeVisible();
|
||||
await expect(docTree.getByText(docChild)).toBeVisible();
|
||||
await expect(docTree.getByText(docChild2)).toBeVisible();
|
||||
|
||||
await docTree.getByText(docChild).click();
|
||||
await verifyDocName(page, docChild);
|
||||
await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport();
|
||||
});
|
||||
|
||||
test('checks resize handle is not present on mobile', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
|
||||
@@ -3,16 +3,6 @@ import path from 'path';
|
||||
|
||||
import { Locator, Page, TestInfo, expect } from '@playwright/test';
|
||||
|
||||
/** Returns a locator for a menu item (handles both menuitem and menuitemradio roles) */
|
||||
export const getMenuItem = (
|
||||
context: Page | Locator,
|
||||
name: string,
|
||||
options?: { exact?: boolean },
|
||||
): Locator =>
|
||||
context
|
||||
.getByRole('menuitem', { name, exact: options?.exact })
|
||||
.or(context.getByRole('menuitemradio', { name, exact: options?.exact }));
|
||||
|
||||
import theme_customization from '../../../../../backend/impress/configuration/theme/default.json';
|
||||
|
||||
export type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
||||
@@ -392,12 +382,12 @@ export async function waitForLanguageSwitch(
|
||||
|
||||
await languagePicker.click();
|
||||
|
||||
await getMenuItem(page, lang.label).click();
|
||||
await page.getByRole('menuitemradio', { name: lang.label }).click();
|
||||
}
|
||||
|
||||
export const clickInEditorMenu = async (page: Page, textButton: string) => {
|
||||
await page.getByRole('button', { name: 'Open the document options' }).click();
|
||||
await getMenuItem(page, textButton).click();
|
||||
await page.getByRole('menuitem', { name: textButton }).click();
|
||||
};
|
||||
|
||||
export const clickInGridMenu = async (
|
||||
@@ -408,7 +398,7 @@ export const clickInGridMenu = async (
|
||||
await row
|
||||
.getByRole('button', { name: /Open the menu of actions for the document/ })
|
||||
.click();
|
||||
await getMenuItem(page, textButton).click();
|
||||
await page.getByRole('menuitem', { name: textButton }).click();
|
||||
};
|
||||
|
||||
export const writeReport = async (
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Page, chromium, expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
BrowserName,
|
||||
getMenuItem,
|
||||
getOtherBrowserName,
|
||||
keyCloakSignIn,
|
||||
verifyDocName,
|
||||
@@ -40,7 +39,7 @@ export const addNewMember = async (
|
||||
|
||||
// Choose a role
|
||||
await page.getByTestId('doc-role-dropdown').click();
|
||||
await getMenuItem(page, role).click();
|
||||
await page.getByRole('menuitemradio', { name: role }).click();
|
||||
await page.getByTestId('doc-share-invite-button').click();
|
||||
|
||||
return users[index].email;
|
||||
@@ -52,7 +51,7 @@ export const updateShareLink = async (
|
||||
linkRole?: LinkRole | null,
|
||||
) => {
|
||||
await page.getByTestId('doc-visibility').click();
|
||||
await getMenuItem(page, linkReach).click();
|
||||
await page.getByRole('menuitemradio', { name: linkReach }).click();
|
||||
|
||||
const visibilityUpdatedText = page
|
||||
.getByText('The document visibility has been updated')
|
||||
@@ -62,7 +61,7 @@ export const updateShareLink = async (
|
||||
|
||||
if (linkRole) {
|
||||
await page.getByTestId('doc-access-mode').click();
|
||||
await getMenuItem(page, linkRole).click();
|
||||
await page.getByRole('menuitemradio', { name: linkRole }).click();
|
||||
await expect(visibilityUpdatedText).toBeVisible();
|
||||
}
|
||||
};
|
||||
@@ -77,7 +76,7 @@ export const updateRoleUser = async (
|
||||
const currentUser = list.getByTestId(`doc-share-member-row-${email}`);
|
||||
const currentUserRole = currentUser.getByTestId('doc-role-dropdown');
|
||||
await currentUserRole.click();
|
||||
await getMenuItem(page, role).click();
|
||||
await page.getByRole('menuitemradio', { name: role }).click();
|
||||
await list.click();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-e2e",
|
||||
"version": "4.8.2",
|
||||
"version": "4.8.4",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-impress",
|
||||
"version": "4.8.2",
|
||||
"version": "4.8.4",
|
||||
"repository": "https://github.com/suitenumerique/docs",
|
||||
"author": "DINUM",
|
||||
"license": "MIT",
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ag-media/react-pdf-table": "2.0.3",
|
||||
"@ai-sdk/openai": "3.0.19",
|
||||
"@ai-sdk/openai": "3.0.45",
|
||||
"@blocknote/code-block": "0.47.1",
|
||||
"@blocknote/core": "0.47.1",
|
||||
"@blocknote/mantine": "0.47.1",
|
||||
@@ -38,20 +38,20 @@
|
||||
"@emoji-mart/data": "1.2.1",
|
||||
"@emoji-mart/react": "1.1.1",
|
||||
"@fontsource-variable/inter": "5.2.8",
|
||||
"@fontsource-variable/material-symbols-outlined": "5.2.35",
|
||||
"@fontsource-variable/material-symbols-outlined": "5.2.38",
|
||||
"@fontsource/material-icons": "5.2.7",
|
||||
"@gouvfr-lasuite/cunningham-react": "4.2.0",
|
||||
"@gouvfr-lasuite/integration": "1.0.3",
|
||||
"@gouvfr-lasuite/ui-kit": "0.19.6",
|
||||
"@gouvfr-lasuite/ui-kit": "0.19.10",
|
||||
"@hocuspocus/provider": "3.4.4",
|
||||
"@mantine/core": "8.3.14",
|
||||
"@mantine/hooks": "8.3.14",
|
||||
"@mantine/core": "8.3.17",
|
||||
"@mantine/hooks": "8.3.17",
|
||||
"@react-aria/live-announcer": "3.4.4",
|
||||
"@react-pdf/renderer": "4.3.1",
|
||||
"@sentry/nextjs": "10.38.0",
|
||||
"@sentry/nextjs": "10.43.0",
|
||||
"@tanstack/react-query": "5.90.21",
|
||||
"@tiptap/extensions": "*",
|
||||
"ai": "6.0.49",
|
||||
"ai": "6.0.128",
|
||||
"canvg": "4.0.3",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
@@ -59,28 +59,28 @@
|
||||
"emoji-datasource-apple": "16.0.0",
|
||||
"emoji-mart": "5.6.0",
|
||||
"emoji-regex": "10.6.0",
|
||||
"i18next": "25.8.12",
|
||||
"i18next": "25.8.18",
|
||||
"i18next-browser-languagedetector": "8.2.1",
|
||||
"idb": "8.0.3",
|
||||
"lodash": "4.17.23",
|
||||
"luxon": "3.7.2",
|
||||
"next": "16.1.6",
|
||||
"posthog-js": "1.347.2",
|
||||
"next": "16.1.7",
|
||||
"posthog-js": "1.360.2",
|
||||
"react": "*",
|
||||
"react-aria-components": "1.15.1",
|
||||
"react-aria-components": "1.16.0",
|
||||
"react-dom": "*",
|
||||
"react-dropzone": "15.0.0",
|
||||
"react-i18next": "16.5.4",
|
||||
"react-intersection-observer": "10.0.2",
|
||||
"react-i18next": "16.5.8",
|
||||
"react-intersection-observer": "10.0.3",
|
||||
"react-resizable-panels": "3.0.6",
|
||||
"react-select": "5.10.2",
|
||||
"styled-components": "6.3.9",
|
||||
"styled-components": "6.3.11",
|
||||
"use-debounce": "10.1.0",
|
||||
"uuid": "13.0.0",
|
||||
"y-protocols": "1.0.7",
|
||||
"yjs": "*",
|
||||
"zod": "3.25.28",
|
||||
"zustand": "5.0.11"
|
||||
"zod": "4.3.6",
|
||||
"zustand": "5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "8.1.0",
|
||||
@@ -89,26 +89,25 @@
|
||||
"@testing-library/jest-dom": "6.9.1",
|
||||
"@testing-library/react": "16.3.2",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@types/lodash": "4.17.23",
|
||||
"@types/lodash": "4.17.24",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"@vitejs/plugin-react": "5.1.4",
|
||||
"@vitejs/plugin-react": "6.0.1",
|
||||
"cross-env": "10.1.0",
|
||||
"dotenv": "17.3.1",
|
||||
"eslint-plugin-docs": "*",
|
||||
"fetch-mock": "9.11.0",
|
||||
"jsdom": "28.1.0",
|
||||
"jsdom": "29.0.0",
|
||||
"node-fetch": "2.7.0",
|
||||
"prettier": "3.8.1",
|
||||
"stylelint": "16.26.1",
|
||||
"stylelint-config-standard": "39.0.1",
|
||||
"stylelint-prettier": "5.0.3",
|
||||
"typescript": "*",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.0.18",
|
||||
"webpack": "5.105.2",
|
||||
"vitest": "4.1.0",
|
||||
"webpack": "5.105.4",
|
||||
"workbox-webpack-plugin": "7.1.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 -960 960 960"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm210-360h60v-180h40v120h60v-120h40v180h60v-200q0-17-11.5-28.5T630-680H450q-17 0-28.5 11.5T410-640v200Zm-50 120v-480 480Z"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 439 B |
@@ -1,17 +1,17 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { Ref, forwardRef } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxType } from './Box';
|
||||
|
||||
export type BoxButtonType = BoxType & {
|
||||
export type BoxButtonType = Omit<BoxType, 'ref'> & {
|
||||
disabled?: boolean;
|
||||
ref?: Ref<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Styleless button that extends the Box component.
|
||||
* Good to wrap around SVGs or other elements that need to be clickable.
|
||||
* Uses aria-disabled instead of native disabled to preserve keyboard focusability.
|
||||
* @param props - @see BoxType props
|
||||
* @param ref
|
||||
* @see Box
|
||||
@@ -22,8 +22,8 @@ export type BoxButtonType = BoxType & {
|
||||
* </BoxButton>
|
||||
* ```
|
||||
*/
|
||||
const BoxButton = forwardRef<HTMLDivElement, BoxButtonType>(
|
||||
({ $css, ...props }, ref) => {
|
||||
const BoxButton = forwardRef<HTMLButtonElement, BoxButtonType>(
|
||||
({ $css, disabled, ...props }, ref) => {
|
||||
const theme = props.$theme || 'gray';
|
||||
const variation = props.$variation || 'primary';
|
||||
|
||||
@@ -31,16 +31,18 @@ const BoxButton = forwardRef<HTMLDivElement, BoxButtonType>(
|
||||
<Box
|
||||
ref={ref}
|
||||
as="button"
|
||||
type="button"
|
||||
$background="none"
|
||||
$margin="none"
|
||||
$padding="none"
|
||||
$hasTransition
|
||||
aria-disabled={disabled || undefined}
|
||||
$css={css`
|
||||
cursor: ${props.disabled ? 'not-allowed' : 'pointer'};
|
||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: inherit;
|
||||
color: ${props.disabled &&
|
||||
color: ${disabled &&
|
||||
`var(--c--contextuals--content--semantic--disabled--primary)`};
|
||||
&:focus-visible {
|
||||
transition: none;
|
||||
@@ -53,11 +55,11 @@ const BoxButton = forwardRef<HTMLDivElement, BoxButtonType>(
|
||||
`}
|
||||
{...props}
|
||||
className={`--docs--box-button ${props.className || ''}`}
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (props.disabled) {
|
||||
onClick={(event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
props.onClick?.(event);
|
||||
props.onClick?.(event as unknown as React.MouseEvent<HTMLDivElement>);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,63 +1,33 @@
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
||||
import { focusMainContentStart } from '@/layouts/utils';
|
||||
|
||||
export const SkipToContent = () => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
// Reset focus after route change so first TAB goes to skip link
|
||||
useEffect(() => {
|
||||
const handleRouteChange = () => {
|
||||
(document.activeElement as HTMLElement)?.blur();
|
||||
|
||||
document.body.setAttribute('tabindex', '-1');
|
||||
document.body.focus({ preventScroll: true });
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeAttribute('tabindex');
|
||||
}, 100);
|
||||
};
|
||||
|
||||
router.events.on('routeChangeComplete', handleRouteChange);
|
||||
return () => {
|
||||
router.events.off('routeChangeComplete', handleRouteChange);
|
||||
};
|
||||
}, [router.events]);
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
const mainContent = document.getElementById(MAIN_LAYOUT_ID);
|
||||
if (mainContent) {
|
||||
mainContent.focus();
|
||||
mainContent.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
const focusTarget = focusMainContentStart();
|
||||
|
||||
if (focusTarget instanceof HTMLElement) {
|
||||
focusTarget.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
$css={css`
|
||||
.c__button--brand--primary.--docs--skip-to-content:focus-visible {
|
||||
box-shadow:
|
||||
0 0 0 1px var(--c--globals--colors--white-000),
|
||||
0 0 0 4px var(--c--contextuals--border--semantic--brand--primary);
|
||||
border-radius: var(--c--globals--spacings--st);
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
type="button"
|
||||
href={`#${MAIN_LAYOUT_ID}`}
|
||||
color="brand"
|
||||
className="--docs--skip-to-content"
|
||||
onClick={handleClick}
|
||||
onFocus={() => setIsVisible(true)}
|
||||
onBlur={() => setIsVisible(false)}
|
||||
style={{
|
||||
@@ -65,7 +35,6 @@ export const SkipToContent = () => {
|
||||
pointerEvents: isVisible ? 'auto' : 'none',
|
||||
position: 'fixed',
|
||||
top: spacingsTokens['2xs'],
|
||||
// padding header + logo(32px) + gap(3xs≈4px) + text "Docs"(≈70px) + 12px
|
||||
left: `calc(${spacingsTokens['base']} + 32px + ${spacingsTokens['3xs']} + 70px + 12px)`,
|
||||
zIndex: 9999,
|
||||
whiteSpace: 'nowrap',
|
||||
|
||||
@@ -26,6 +26,7 @@ import { useDropdownKeyboardNav } from './hook/useDropdownKeyboardNav';
|
||||
export type DropdownMenuOption = {
|
||||
icon?: ReactNode;
|
||||
label: string;
|
||||
lang?: string;
|
||||
testId?: string;
|
||||
value?: string;
|
||||
callback?: () => void | Promise<unknown>;
|
||||
@@ -69,7 +70,10 @@ export const DropdownMenu = ({
|
||||
const [isOpen, setIsOpen] = useState(opened ?? false);
|
||||
const [focusedIndex, setFocusedIndex] = useState(-1);
|
||||
const blockButtonRef = useRef<HTMLDivElement>(null);
|
||||
const menuItemRefs = useRef<(HTMLDivElement | null)[]>([]);
|
||||
const menuItemRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
||||
const isSingleSelectable = options.some(
|
||||
(option) => option.isSelected !== undefined,
|
||||
);
|
||||
|
||||
const onOpenChange = useCallback(
|
||||
(isOpen: boolean) => {
|
||||
@@ -110,10 +114,6 @@ export const DropdownMenu = ({
|
||||
[onOpenChange],
|
||||
);
|
||||
|
||||
const hasSelectable =
|
||||
selectedValues !== undefined ||
|
||||
options.some((option) => option.isSelected !== undefined);
|
||||
|
||||
if (disabled) {
|
||||
return children;
|
||||
}
|
||||
@@ -176,20 +176,25 @@ export const DropdownMenu = ({
|
||||
}
|
||||
const isDisabled = option.disabled !== undefined && option.disabled;
|
||||
const isFocused = index === focusedIndex;
|
||||
const ariaChecked = hasSelectable
|
||||
? option.isSelected ||
|
||||
selectedValues?.includes(option.value ?? '') ||
|
||||
false
|
||||
: undefined;
|
||||
const isSelected =
|
||||
option.isSelected === true ||
|
||||
(selectedValues?.includes(option.value ?? '') ?? false);
|
||||
const itemRole =
|
||||
selectedValues !== undefined
|
||||
? 'menuitemcheckbox'
|
||||
: isSingleSelectable
|
||||
? 'menuitemradio'
|
||||
: 'menuitem';
|
||||
const optionKey = option.value ?? option.testId ?? `option-${index}`;
|
||||
|
||||
return (
|
||||
<Fragment key={option.label}>
|
||||
<Fragment key={optionKey}>
|
||||
<BoxButton
|
||||
ref={(el) => {
|
||||
menuItemRefs.current[index] = el;
|
||||
}}
|
||||
role={hasSelectable ? 'menuitemradio' : 'menuitem'}
|
||||
aria-checked={ariaChecked}
|
||||
role={itemRole}
|
||||
aria-checked={itemRole === 'menuitem' ? undefined : isSelected}
|
||||
data-testid={option.testId}
|
||||
$direction="row"
|
||||
disabled={isDisabled}
|
||||
@@ -200,7 +205,6 @@ export const DropdownMenu = ({
|
||||
triggerOption(option);
|
||||
}}
|
||||
onKeyDown={keyboardAction(() => triggerOption(option))}
|
||||
key={option.label}
|
||||
$align="center"
|
||||
$justify="space-between"
|
||||
$background="var(--c--contextuals--background--surface--primary)"
|
||||
@@ -276,11 +280,10 @@ export const DropdownMenu = ({
|
||||
</Box>
|
||||
)}
|
||||
<Text $variation={isDisabled ? 'tertiary' : 'primary'}>
|
||||
{option.label}
|
||||
<span lang={option.lang}>{option.label}</span>
|
||||
</Text>
|
||||
</Box>
|
||||
{(option.isSelected ||
|
||||
selectedValues?.includes(option.value ?? '')) && (
|
||||
{isSelected && (
|
||||
<Icon
|
||||
iconName="check"
|
||||
$size="20px"
|
||||
|
||||
+6
-6
@@ -58,7 +58,7 @@ describe('<DropdownMenu />', () => {
|
||||
expect(radios[2]).toHaveAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
test('renders menuitemradio role with aria-checked when selectedValues is provided', async () => {
|
||||
test('renders menuitemcheckbox role with aria-checked when selectedValues is provided', async () => {
|
||||
const optionsWithValues: DropdownMenuOption[] = [
|
||||
{ label: 'English', value: 'en', callback: vi.fn() },
|
||||
{ label: 'Français', value: 'fr', callback: vi.fn() },
|
||||
@@ -77,12 +77,12 @@ describe('<DropdownMenu />', () => {
|
||||
{ wrapper: AppWrapper },
|
||||
);
|
||||
|
||||
const radios = screen.getAllByRole('menuitemradio');
|
||||
expect(radios).toHaveLength(3);
|
||||
const checkboxes = screen.getAllByRole('menuitemcheckbox');
|
||||
expect(checkboxes).toHaveLength(3);
|
||||
|
||||
expect(radios[0]).toHaveAttribute('aria-checked', 'false');
|
||||
expect(radios[1]).toHaveAttribute('aria-checked', 'true');
|
||||
expect(radios[2]).toHaveAttribute('aria-checked', 'false');
|
||||
expect(checkboxes[0]).toHaveAttribute('aria-checked', 'false');
|
||||
expect(checkboxes[1]).toHaveAttribute('aria-checked', 'true');
|
||||
expect(checkboxes[2]).toHaveAttribute('aria-checked', 'false');
|
||||
});
|
||||
|
||||
test('trigger button has aria-haspopup and aria-expanded', async () => {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ type UseDropdownKeyboardNavProps = {
|
||||
isOpen: boolean;
|
||||
focusedIndex: number;
|
||||
options: DropdownMenuOption[];
|
||||
menuItemRefs: RefObject<(HTMLDivElement | null)[]>;
|
||||
menuItemRefs: RefObject<(HTMLButtonElement | null)[]>;
|
||||
setFocusedIndex: (index: number) => void;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ export type QuickSearchProps = {
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
groupKey?: string;
|
||||
beforeList?: ReactNode;
|
||||
};
|
||||
|
||||
export const QuickSearch = ({
|
||||
@@ -41,6 +42,7 @@ export const QuickSearch = ({
|
||||
showInput = true,
|
||||
label,
|
||||
placeholder,
|
||||
beforeList,
|
||||
children,
|
||||
}: PropsWithChildren<QuickSearchProps>) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
@@ -76,6 +78,7 @@ export const QuickSearch = ({
|
||||
{inputContent}
|
||||
</QuickSearchInput>
|
||||
)}
|
||||
{beforeList}
|
||||
<Command.List id={listId} aria-label={label} role="listbox">
|
||||
<Box>{children}</Box>
|
||||
</Command.List>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { PropsWithChildren, useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { HorizontalSeparator } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useFocusStore } from '@/stores';
|
||||
|
||||
import { Box } from '../Box';
|
||||
import { Icon } from '../Icon';
|
||||
@@ -14,7 +15,6 @@ type QuickSearchInputProps = {
|
||||
placeholder?: string;
|
||||
withSeparator?: boolean;
|
||||
listId?: string;
|
||||
isExpanded?: boolean;
|
||||
};
|
||||
export const QuickSearchInput = ({
|
||||
inputValue,
|
||||
@@ -26,6 +26,12 @@ export const QuickSearchInput = ({
|
||||
}: PropsWithChildren<QuickSearchInputProps>) => {
|
||||
const { t } = useTranslation();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const addLastFocus = useFocusStore((state) => state.addLastFocus);
|
||||
|
||||
useEffect(() => {
|
||||
addLastFocus(inputRef.current);
|
||||
}, [addLastFocus]);
|
||||
|
||||
if (children) {
|
||||
return (
|
||||
@@ -42,11 +48,12 @@ export const QuickSearchInput = ({
|
||||
$direction="row"
|
||||
$align="center"
|
||||
className="quick-search-input"
|
||||
$gap={spacingsTokens['2xs']}
|
||||
$gap={spacingsTokens['xxs']}
|
||||
$padding={{ horizontal: 'base', vertical: 'xxs' }}
|
||||
>
|
||||
<Icon iconName="search" $variation="secondary" aria-hidden="true" />
|
||||
<Command.Input
|
||||
ref={inputRef}
|
||||
autoFocus={true}
|
||||
aria-label={t('Quick search input')}
|
||||
aria-controls={listId}
|
||||
@@ -55,6 +62,7 @@ export const QuickSearchInput = ({
|
||||
placeholder={placeholder ?? t('Search')}
|
||||
onValueChange={onFilter}
|
||||
maxLength={254}
|
||||
minLength={6}
|
||||
data-testid="quick-search-input"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -18,14 +18,15 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
[cmdk-input] {
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
font-size: 16px;
|
||||
background: white;
|
||||
outline: none;
|
||||
color: var(--c--contextuals--content--semantic--neutral--primary);
|
||||
border-radius: var(--c--globals--spacings--0);
|
||||
font-family: var(--c--globals--font--families--base);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--c--globals--colors--gray-500);
|
||||
color: var(--c--contextuals--content--semantic--neutral--tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import clsx from 'clsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Box, Loading } from '@/components';
|
||||
import { DocHeader, FloatingBar } from '@/docs/doc-header/';
|
||||
import { DocHeader } from '@/docs/doc-header/';
|
||||
import {
|
||||
Doc,
|
||||
LinkReach,
|
||||
@@ -35,7 +35,6 @@ export const DocEditorContainer = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{isDesktop && <FloatingBar />}
|
||||
<Box
|
||||
$maxWidth="868px"
|
||||
$width="100%"
|
||||
|
||||
+29
-4
@@ -8,12 +8,37 @@ export const cssComments = (
|
||||
& .--docs--main-editor .ProseMirror {
|
||||
// Comments marks in the editor
|
||||
.bn-editor {
|
||||
.bn-thread-mark:not([data-orphan='true']),
|
||||
.bn-thread-mark-selected:not([data-orphan='true']) {
|
||||
background: ${canSeeComment ? '#EDB40066' : 'transparent'};
|
||||
color: var(--c--globals--colors--gray-700);
|
||||
// Resets blocknote comments styles
|
||||
.bn-thread-mark,
|
||||
.bn-thread-mark-selected {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
${canSeeComment &&
|
||||
css`
|
||||
.bn-thread-mark:not([data-orphan='true']) {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--c--contextuals--background--palette--yellow--tertiary) 40%,
|
||||
transparent
|
||||
);
|
||||
border-bottom: 2px solid
|
||||
var(--c--contextuals--background--palette--yellow--secondary);
|
||||
|
||||
mix-blend-mode: multiply;
|
||||
|
||||
transition:
|
||||
background-color var(--c--globals--transitions--duration),
|
||||
border-bottom-color var(--c--globals--transitions--duration);
|
||||
|
||||
&:has(.bn-thread-mark-selected) {
|
||||
background-color: var(
|
||||
--c--contextuals--background--palette--yellow--tertiary
|
||||
);
|
||||
}
|
||||
}
|
||||
`}
|
||||
|
||||
[data-show-selection] {
|
||||
color: HighlightText;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { useUpdateDoc } from '@/docs/doc-management/';
|
||||
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning';
|
||||
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning/api/useDocVersions';
|
||||
import { toBase64 } from '@/utils/string';
|
||||
import { isFirefox } from '@/utils/userAgent';
|
||||
|
||||
|
||||
@@ -1,11 +1,83 @@
|
||||
import { useEffect } from 'react';
|
||||
import { announce } from '@react-aria/live-announcer';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { DocsBlockNoteEditor } from '../types';
|
||||
|
||||
const getFormattingShortcutLabel = (
|
||||
event: KeyboardEvent,
|
||||
t: (key: string) => string,
|
||||
): string | null => {
|
||||
const isMod = event.ctrlKey || event.metaKey;
|
||||
if (!isMod) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (event.altKey) {
|
||||
switch (event.code) {
|
||||
case 'Digit1':
|
||||
return t('Heading 1 applied');
|
||||
case 'Digit2':
|
||||
return t('Heading 2 applied');
|
||||
case 'Digit3':
|
||||
return t('Heading 3 applied');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.shiftKey) {
|
||||
switch (event.code) {
|
||||
case 'Digit0':
|
||||
return t('Paragraph applied');
|
||||
case 'Digit6':
|
||||
return t('Toggle list applied');
|
||||
case 'Digit7':
|
||||
return t('Numbered list applied');
|
||||
case 'Digit8':
|
||||
return t('Bulleted list applied');
|
||||
case 'Digit9':
|
||||
return t('Checklist applied');
|
||||
case 'KeyC':
|
||||
return t('Code block applied');
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const useShortcuts = (
|
||||
editor: DocsBlockNoteEditor,
|
||||
el: HTMLDivElement | null,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleFormattingShortcut = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (!editor?.isFocused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const label = getFormattingShortcutLabel(event, t);
|
||||
if (label) {
|
||||
setTimeout(() => {
|
||||
announce(label, 'assertive');
|
||||
}, 150);
|
||||
}
|
||||
},
|
||||
[editor, t],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
el?.addEventListener('keydown', handleFormattingShortcut, true);
|
||||
|
||||
return () => {
|
||||
el?.removeEventListener('keydown', handleFormattingShortcut, true);
|
||||
};
|
||||
}, [el, handleFormattingShortcut]);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if editor and its view are mounted
|
||||
if (!editor || !el) {
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@/docs/doc-export/components/ModalExport', () => ({
|
||||
ModalExport: vi.fn(),
|
||||
}));
|
||||
|
||||
const originalEnv = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT;
|
||||
|
||||
describe('useModuleExport', () => {
|
||||
@@ -16,12 +21,12 @@ describe('useModuleExport', () => {
|
||||
const Export = await import('@/features/docs/doc-export/');
|
||||
|
||||
expect(Export.default).toBeUndefined();
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
it('should load modules when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false';
|
||||
const Export = await import('@/features/docs/doc-export/');
|
||||
|
||||
expect(Export.default).toHaveProperty('ModalExport');
|
||||
}, 15000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,6 +60,23 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
|
||||
const { untitledDocument } = useTrans();
|
||||
const mediaUrl = useMediaUrl();
|
||||
|
||||
const formatOptions = [
|
||||
{ label: t('PDF'), value: DocDownloadFormat.PDF },
|
||||
{ label: t('Docx'), value: DocDownloadFormat.DOCX },
|
||||
{ label: t('ODT'), value: DocDownloadFormat.ODT },
|
||||
{ label: t('HTML'), value: DocDownloadFormat.HTML },
|
||||
{ label: t('Print'), value: DocDownloadFormat.PRINT },
|
||||
];
|
||||
|
||||
const formatLabels = Object.fromEntries(
|
||||
formatOptions.map((opt) => [opt.value, opt.label]),
|
||||
);
|
||||
|
||||
const downloadButtonAriaLabel =
|
||||
format === DocDownloadFormat.PRINT
|
||||
? t('Print')
|
||||
: t('Download {{format}}', { format: formatLabels[format] });
|
||||
|
||||
async function onSubmit() {
|
||||
if (!editor) {
|
||||
toast(t('The export failed'), VariantType.ERROR);
|
||||
@@ -211,9 +228,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="doc-export-download-button"
|
||||
aria-label={
|
||||
format === DocDownloadFormat.PRINT ? t('Print') : t('Download')
|
||||
}
|
||||
aria-label={downloadButtonAriaLabel}
|
||||
variant="primary"
|
||||
fullWidth
|
||||
onClick={() => void onSubmit()}
|
||||
@@ -260,13 +275,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
|
||||
clearable={false}
|
||||
fullWidth
|
||||
label={t('Format')}
|
||||
options={[
|
||||
{ label: t('PDF'), value: DocDownloadFormat.PDF },
|
||||
{ label: t('Docx'), value: DocDownloadFormat.DOCX },
|
||||
{ label: t('ODT'), value: DocDownloadFormat.ODT },
|
||||
{ label: t('HTML'), value: DocDownloadFormat.HTML },
|
||||
{ label: t('Print'), value: DocDownloadFormat.PRINT },
|
||||
]}
|
||||
options={formatOptions}
|
||||
value={format}
|
||||
onChange={(options) =>
|
||||
setFormat(options.target.value as DocDownloadFormat)
|
||||
|
||||
+3
-11
@@ -1,6 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { afterAll, beforeEach, describe, expect, vi } from 'vitest';
|
||||
|
||||
import { AppWrapper } from '@/tests/utils';
|
||||
@@ -40,17 +38,11 @@ describe('DocToolBox - Licence', () => {
|
||||
render(<DocToolBox doc={doc as any} />, {
|
||||
wrapper: AppWrapper,
|
||||
});
|
||||
const optionsButton = await screen.findByLabelText('Export the document');
|
||||
await userEvent.click(optionsButton);
|
||||
|
||||
// Wait for the export modal to be visible, then assert on its content text.
|
||||
await screen.findByTestId('modal-export-title');
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Export your document to print or download in .docx, .odt, .pdf or .html(zip) format.',
|
||||
),
|
||||
await screen.findByLabelText('Export the document'),
|
||||
).toBeInTheDocument();
|
||||
}, 10000);
|
||||
}, 15000);
|
||||
|
||||
test('The export button is not rendered when MIT version is activated', async () => {
|
||||
process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'true';
|
||||
@@ -68,5 +60,5 @@ describe('DocToolBox - Licence', () => {
|
||||
expect(
|
||||
screen.queryByLabelText('Export the document'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Button, useModal } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useTreeContext } from '@gouvfr-lasuite/ui-kit';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
@@ -17,6 +16,7 @@ import GroupSVG from '@/assets/icons/ui-kit/group.svg';
|
||||
import HistorySVG from '@/assets/icons/ui-kit/history.svg';
|
||||
import KeepSVG from '@/assets/icons/ui-kit/keep.svg';
|
||||
import KeepOffSVG from '@/assets/icons/ui-kit/keep_off.svg';
|
||||
import MarkdownCopySVG from '@/assets/icons/ui-kit/markdown_copy.svg';
|
||||
import {
|
||||
Box,
|
||||
DropdownMenu,
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
useDocUtils,
|
||||
useDuplicateDoc,
|
||||
} from '@/docs/doc-management';
|
||||
import { KEY_LIST_DOC_VERSIONS } from '@/docs/doc-versioning';
|
||||
import { useFocusStore, useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useCopyCurrentEditorToClipboard } from '../hooks/useCopyCurrentEditorToClipboard';
|
||||
@@ -87,7 +86,6 @@ interface DocToolBoxProps {
|
||||
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
const { t } = useTranslation();
|
||||
const treeContext = useTreeContext<Doc>();
|
||||
const queryClient = useQueryClient();
|
||||
const router = useRouter();
|
||||
const { isChild, isTopRoot } = useDocUtils(doc);
|
||||
|
||||
@@ -113,16 +111,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
listInvalidQueries: [KEY_LIST_DOC, KEY_DOC, KEY_LIST_FAVORITE_DOC],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (selectHistoryModal.isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [KEY_LIST_DOC_VERSIONS],
|
||||
});
|
||||
}, [selectHistoryModal.isOpen, queryClient]);
|
||||
|
||||
// Emoji Management
|
||||
const { emoji } = getEmojiAndTitle(doc.title ?? '');
|
||||
const { updateDocEmoji } = useDocTitleUpdate();
|
||||
@@ -130,13 +118,13 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
const options: DropdownMenuOption[] = [
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: <GroupSVG width={24} height={24} />,
|
||||
icon: <GroupSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: modalShare.open,
|
||||
show: isSmallMobile,
|
||||
},
|
||||
{
|
||||
label: t('Export'),
|
||||
icon: <DownloadSVG width={24} height={24} />,
|
||||
icon: <DownloadSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => {
|
||||
setIsModalExportOpen(true);
|
||||
},
|
||||
@@ -145,9 +133,9 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
{
|
||||
label: doc.is_favorite ? t('Unpin') : t('Pin'),
|
||||
icon: doc.is_favorite ? (
|
||||
<KeepOffSVG width={24} height={24} />
|
||||
<KeepOffSVG width={24} height={24} aria-hidden="true" />
|
||||
) : (
|
||||
<KeepSVG width={24} height={24} />
|
||||
<KeepSVG width={24} height={24} aria-hidden="true" />
|
||||
),
|
||||
callback: () => {
|
||||
if (doc.is_favorite) {
|
||||
@@ -160,7 +148,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Version history'),
|
||||
icon: <HistorySVG width={24} height={24} />,
|
||||
icon: <HistorySVG width={24} height={24} aria-hidden="true" />,
|
||||
disabled: !doc.abilities.versions_list,
|
||||
callback: () => {
|
||||
selectHistoryModal.open();
|
||||
@@ -170,7 +158,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Remove emoji'),
|
||||
icon: <RemoveEmojiSVG width={24} height={24} />,
|
||||
icon: <RemoveEmojiSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => {
|
||||
updateDocEmoji(doc.id, doc.title ?? '', '');
|
||||
},
|
||||
@@ -179,7 +167,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Add emoji'),
|
||||
icon: <AddEmojiSVG width={24} height={24} />,
|
||||
icon: <AddEmojiSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => {
|
||||
updateDocEmoji(doc.id, doc.title ?? '', '📄');
|
||||
},
|
||||
@@ -188,12 +176,12 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Copy link'),
|
||||
icon: <AddLinkSVG width={24} height={24} />,
|
||||
icon: <AddLinkSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: copyDocLink,
|
||||
},
|
||||
{
|
||||
label: t('Copy as {{format}}', { format: 'Markdown' }),
|
||||
icon: <ContentCopySVG width={24} height={24} />,
|
||||
icon: <MarkdownCopySVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => {
|
||||
void copyCurrentEditorToClipboard('markdown');
|
||||
},
|
||||
@@ -201,7 +189,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Duplicate'),
|
||||
icon: <ContentCopySVG width={24} height={24} />,
|
||||
icon: <ContentCopySVG width={24} height={24} aria-hidden="true" />,
|
||||
disabled: !doc.abilities.duplicate,
|
||||
callback: () => {
|
||||
duplicateDoc({
|
||||
@@ -214,7 +202,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
},
|
||||
{
|
||||
label: isChild ? t('Delete sub-document') : t('Delete document'),
|
||||
icon: <DeleteSVG width={24} height={24} />,
|
||||
icon: <DeleteSVG width={24} height={24} aria-hidden="true" />,
|
||||
disabled: !doc.abilities.destroy,
|
||||
callback: () => {
|
||||
setIsModalRemoveOpen(true);
|
||||
|
||||
+17
-6
@@ -4,7 +4,7 @@ import {
|
||||
} from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useEditorStore } from '../../doc-editor';
|
||||
import { useEditorStore } from '@/docs/doc-editor/stores/useEditorStore';
|
||||
|
||||
export const useCopyCurrentEditorToClipboard = () => {
|
||||
const { editor } = useEditorStore();
|
||||
@@ -13,20 +13,31 @@ export const useCopyCurrentEditorToClipboard = () => {
|
||||
|
||||
return async (asFormat: 'html' | 'markdown') => {
|
||||
if (!editor) {
|
||||
toast(t('Editor unavailable'), VariantType.ERROR, { duration: 3000 });
|
||||
const message = t('Editor unavailable');
|
||||
toast(message, VariantType.ERROR, { duration: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const editorContentFormatted =
|
||||
asFormat === 'html'
|
||||
? await editor.blocksToHTMLLossy()
|
||||
: await editor.blocksToMarkdownLossy();
|
||||
? editor.blocksToHTMLLossy()
|
||||
: editor.blocksToMarkdownLossy();
|
||||
await navigator.clipboard.writeText(editorContentFormatted);
|
||||
toast(t('Copied to clipboard'), VariantType.SUCCESS, { duration: 3000 });
|
||||
const successMessage =
|
||||
asFormat === 'markdown'
|
||||
? t('Copied as Markdown to clipboard')
|
||||
: t('Copied to clipboard');
|
||||
|
||||
toast(successMessage, VariantType.SUCCESS, { duration: 3000 });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast(t('Failed to copy to clipboard'), VariantType.ERROR, {
|
||||
const errorMessage =
|
||||
asFormat === 'markdown'
|
||||
? t('Failed to copy as Markdown to clipboard')
|
||||
: t('Failed to copy to clipboard');
|
||||
|
||||
toast(errorMessage, VariantType.ERROR, {
|
||||
duration: 3000,
|
||||
});
|
||||
}
|
||||
|
||||
+12
@@ -1,4 +1,6 @@
|
||||
import { announce } from '@react-aria/live-announcer';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
|
||||
@@ -29,6 +31,8 @@ export function useCreateFavoriteDoc({
|
||||
listInvalidQueries,
|
||||
}: CreateFavoriteDocProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMutation<void, APIError, CreateFavoriteDocParams>({
|
||||
mutationFn: createFavoriteDoc,
|
||||
onSuccess: () => {
|
||||
@@ -37,7 +41,15 @@ export function useCreateFavoriteDoc({
|
||||
queryKey: [queryKey],
|
||||
});
|
||||
});
|
||||
|
||||
const message = t('Document pinned successfully!');
|
||||
announce(message, 'polite');
|
||||
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: () => {
|
||||
const message = t('Failed to pin the document.');
|
||||
announce(message, 'assertive');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+12
@@ -1,4 +1,6 @@
|
||||
import { announce } from '@react-aria/live-announcer';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
|
||||
@@ -29,6 +31,8 @@ export function useDeleteFavoriteDoc({
|
||||
listInvalidQueries,
|
||||
}: DeleteFavoriteDocProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useMutation<void, APIError, DeleteFavoriteDocParams>({
|
||||
mutationFn: deleteFavoriteDoc,
|
||||
onSuccess: () => {
|
||||
@@ -37,7 +41,15 @@ export function useDeleteFavoriteDoc({
|
||||
queryKey: [queryKey],
|
||||
});
|
||||
});
|
||||
|
||||
const message = t('Document unpinned successfully!');
|
||||
announce(message, 'polite');
|
||||
|
||||
onSuccess?.();
|
||||
},
|
||||
onError: () => {
|
||||
const message = t('Failed to unpin the document.');
|
||||
announce(message, 'assertive');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,14 +88,16 @@ export function useDuplicateDoc(options?: DuplicateDocOptions) {
|
||||
queryKey: [KEY_LIST_DOC],
|
||||
});
|
||||
|
||||
toast(t('Document duplicated successfully!'), VariantType.SUCCESS, {
|
||||
const message = t('Document duplicated successfully!');
|
||||
toast(message, VariantType.SUCCESS, {
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
void options?.onSuccess?.(data, variables, onMutateResult, context);
|
||||
},
|
||||
onError: (error, variables, onMutateResult, context) => {
|
||||
toast(t('Failed to duplicate the document...'), VariantType.ERROR, {
|
||||
const message = t('Failed to duplicate the document...');
|
||||
toast(message, VariantType.ERROR, {
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export function useUpdateDoc(queryConfig?: UseUpdateDoc) {
|
||||
...queryConfig,
|
||||
onSuccess: (data, variables, onMutateResult, context) => {
|
||||
queryConfig?.listInvalidQueries?.forEach((queryKey) => {
|
||||
void queryClient.invalidateQueries({
|
||||
void queryClient.resetQueries({
|
||||
queryKey: [queryKey],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ export const DocIcon = ({
|
||||
const { t } = useTranslation();
|
||||
const { addLastFocus, restoreFocus } = useFocusStore();
|
||||
|
||||
const iconRef = useRef<HTMLDivElement>(null);
|
||||
const iconRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [openEmojiPicker, setOpenEmojiPicker] = useState<boolean>(false);
|
||||
const [pickerPosition, setPickerPosition] = useState<{
|
||||
|
||||
+5
@@ -14,6 +14,11 @@ vi.mock('@/stores', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@gouvfr-lasuite/ui-kit', async () => ({
|
||||
...(await vi.importActual('@gouvfr-lasuite/ui-kit')),
|
||||
useTreeContext: () => null,
|
||||
}));
|
||||
|
||||
describe('useDocTitleUpdate', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
+17
-1
@@ -30,6 +30,8 @@ const defaultValues = {
|
||||
|
||||
type ExtendedCloseEvent = CloseEvent & { wasClean: boolean };
|
||||
|
||||
let reconnectTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
||||
...defaultValues,
|
||||
createProvider: (wsUrl, storeId, initialDoc) => {
|
||||
@@ -48,7 +50,20 @@ export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
||||
onDisconnect(data) {
|
||||
// Attempt to reconnect if the disconnection was clean (initiated by the client or server)
|
||||
if ((data.event as ExtendedCloseEvent).wasClean) {
|
||||
void provider.connect();
|
||||
if (data.event.reason === 'No cookies' && data.event.code === 4001) {
|
||||
console.error(
|
||||
'Disconnection due to missing cookies. Not attempting to reconnect.',
|
||||
);
|
||||
void provider.disconnect();
|
||||
set({
|
||||
isReady: true,
|
||||
isConnected: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(reconnectTimeout);
|
||||
reconnectTimeout = setTimeout(() => void provider.connect(), 1000);
|
||||
}
|
||||
},
|
||||
onAuthenticationFailed() {
|
||||
@@ -107,6 +122,7 @@ export const useProviderStore = create<UseCollaborationStore>((set, get) => ({
|
||||
return provider;
|
||||
},
|
||||
destroyProvider: () => {
|
||||
clearTimeout(reconnectTimeout);
|
||||
const provider = get().provider;
|
||||
if (provider) {
|
||||
provider.destroy();
|
||||
|
||||
+11
-1
@@ -1,3 +1,4 @@
|
||||
import { announce } from '@react-aria/live-announcer';
|
||||
import { t } from 'i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { InView } from 'react-intersection-observer';
|
||||
@@ -73,10 +74,12 @@ export const DocSearchContent = ({
|
||||
docs = docs.filter(filterResults);
|
||||
}
|
||||
|
||||
const elements = search || isSearchNotMandatory ? docs : [];
|
||||
|
||||
setDocsData({
|
||||
groupName: docs.length > 0 ? groupName : '',
|
||||
groupKey: 'docs',
|
||||
elements: search || isSearchNotMandatory ? docs : [],
|
||||
elements,
|
||||
emptyString: t('No document found'),
|
||||
endActions: hasNextPage
|
||||
? [
|
||||
@@ -90,6 +93,13 @@ export const DocSearchContent = ({
|
||||
]
|
||||
: [],
|
||||
});
|
||||
|
||||
if (search) {
|
||||
announce(
|
||||
t('{{count}} result(s) available', { count: elements.length }),
|
||||
'polite',
|
||||
);
|
||||
}
|
||||
}, [
|
||||
search,
|
||||
data?.pages,
|
||||
|
||||
+7
-1
@@ -58,7 +58,13 @@ export const DocSearchFilters = ({
|
||||
/>
|
||||
</Box>
|
||||
{hasFilters && (
|
||||
<Button color="brand" variant="tertiary" size="small" onClick={onReset}>
|
||||
<Button
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
size="small"
|
||||
onClick={onReset}
|
||||
aria-label={t('Reset search filters')}
|
||||
>
|
||||
{t('Reset')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
+17
-13
@@ -14,7 +14,7 @@ import {
|
||||
DocSearchFiltersValues,
|
||||
DocSearchTarget,
|
||||
} from '@/docs/doc-search';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
import { useFocusStore, useResponsiveStore } from '@/stores';
|
||||
|
||||
import EmptySearchIcon from '../assets/illustration-docs-empty.png';
|
||||
|
||||
@@ -36,6 +36,7 @@ const DocSearchModalGlobal = ({
|
||||
}: DocSearchModalGlobalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const restoreFocus = useFocusStore((state) => state.restoreFocus);
|
||||
const router = useRouter();
|
||||
const [search, setSearch] = useState('');
|
||||
const [filters, setFilters] = useState<DocSearchFiltersValues>(
|
||||
@@ -51,6 +52,7 @@ const DocSearchModalGlobal = ({
|
||||
|
||||
const handleResetFilters = () => {
|
||||
setFilters({});
|
||||
restoreFocus();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -62,6 +64,7 @@ const DocSearchModalGlobal = ({
|
||||
aria-describedby="doc-search-modal-title"
|
||||
>
|
||||
<Box
|
||||
aria-label={t('Search modal')}
|
||||
$direction="column"
|
||||
$justify="space-between"
|
||||
className="--docs--doc-search-modal"
|
||||
@@ -85,21 +88,26 @@ const DocSearchModalGlobal = ({
|
||||
/>
|
||||
</Box>
|
||||
<QuickSearch
|
||||
label={t('Search documents')}
|
||||
placeholder={t('Type the name of a document')}
|
||||
loading={loading}
|
||||
onFilter={handleInputSearch}
|
||||
beforeList={
|
||||
showFilters ? (
|
||||
<Box $padding={{ horizontal: '10px' }}>
|
||||
<DocSearchFilters
|
||||
values={filters}
|
||||
onValuesChange={setFilters}
|
||||
onReset={handleResetFilters}
|
||||
/>
|
||||
</Box>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<Box
|
||||
$padding={{ horizontal: '10px', vertical: 'base' }}
|
||||
$height={isDesktop ? '500px' : 'calc(100vh - 68px - 1rem)'}
|
||||
>
|
||||
{showFilters && (
|
||||
<DocSearchFilters
|
||||
values={filters}
|
||||
onValuesChange={setFilters}
|
||||
onReset={handleResetFilters}
|
||||
/>
|
||||
)}
|
||||
{search.length === 0 && (
|
||||
<Box
|
||||
$direction="column"
|
||||
@@ -107,11 +115,7 @@ const DocSearchModalGlobal = ({
|
||||
$align="center"
|
||||
$justify="center"
|
||||
>
|
||||
<Image
|
||||
width={320}
|
||||
src={EmptySearchIcon}
|
||||
alt={t('No active search')}
|
||||
/>
|
||||
<Image width={320} src={EmptySearchIcon} alt="" />
|
||||
</Box>
|
||||
)}
|
||||
{search && (
|
||||
|
||||
+2
-1
@@ -124,7 +124,8 @@ export const DocShareAddMemberList = ({
|
||||
$scope="surface"
|
||||
$theme="tertiary"
|
||||
$variation=""
|
||||
$border="1px solid var(--c--contextuals--border--semantic--contextual--primary)"
|
||||
$border="1px solid var(--c--contextuals--border--surface--primary)"
|
||||
$margin={{ bottom: 'sm' }}
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
|
||||
@@ -289,7 +289,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && isRootDoc && (
|
||||
<Box $padding={{ horizontal: 'base' }}>
|
||||
<Box $padding={{ horizontal: 'base', top: 'base' }}>
|
||||
<QuickSearchGroupAccessRequest doc={doc} />
|
||||
<QuickSearchGroupInvitation doc={doc} />
|
||||
<QuickSearchGroupMember doc={doc} />
|
||||
@@ -301,6 +301,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
||||
searchUsersRawData={searchUsersQuery.data}
|
||||
onSelect={onSelect}
|
||||
userQuery={userQuery}
|
||||
minLength={API_USERS_SEARCH_QUERY_MIN_LENGTH}
|
||||
/>
|
||||
)}
|
||||
</QuickSearch>
|
||||
@@ -321,14 +322,35 @@ interface QuickSearchInviteInputSectionProps {
|
||||
onSelect: (usr: User) => void;
|
||||
searchUsersRawData: User[] | undefined;
|
||||
userQuery: string;
|
||||
minLength: number;
|
||||
}
|
||||
|
||||
const QuickSearchInviteInputSection = ({
|
||||
onSelect,
|
||||
searchUsersRawData,
|
||||
userQuery,
|
||||
minLength,
|
||||
}: QuickSearchInviteInputSectionProps) => {
|
||||
const { t } = useTranslation();
|
||||
const hint = useMemo(() => {
|
||||
if (userQuery.length < minLength) {
|
||||
return t('Type at least {{minLength}} characters to display user names', {
|
||||
minLength,
|
||||
});
|
||||
}
|
||||
if (isValidEmail(userQuery)) {
|
||||
return t('Choose the email');
|
||||
}
|
||||
if (!searchUsersRawData?.length) {
|
||||
return t('No results. Type a full email address to invite someone.');
|
||||
}
|
||||
|
||||
return t('Choose a user');
|
||||
}, [minLength, searchUsersRawData?.length, t, userQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
announce(hint, 'polite');
|
||||
}, [hint]);
|
||||
|
||||
const searchUserData: QuickSearchData<User> = useMemo(() => {
|
||||
const users = searchUsersRawData || [];
|
||||
@@ -347,7 +369,7 @@ const QuickSearchInviteInputSection = ({
|
||||
);
|
||||
|
||||
return {
|
||||
groupName: t('Search user result'),
|
||||
groupName: hint,
|
||||
elements: users,
|
||||
endActions:
|
||||
isEmail && !hasEmailInUsers
|
||||
@@ -359,12 +381,12 @@ const QuickSearchInviteInputSection = ({
|
||||
]
|
||||
: undefined,
|
||||
};
|
||||
}, [onSelect, searchUsersRawData, t, userQuery]);
|
||||
}, [searchUsersRawData, userQuery, hint, onSelect]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
aria-label={t('List search user result card')}
|
||||
$padding={{ horizontal: 'base', bottom: '3xs' }}
|
||||
$padding={{ horizontal: 'base', bottom: '3xs', top: 'base' }}
|
||||
>
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
|
||||
+7
-5
@@ -51,11 +51,13 @@ export const Heading = ({
|
||||
|
||||
editor.setTextCursorPosition(headingId, 'end');
|
||||
|
||||
document.querySelector(`[data-id="${headingId}"]`)?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'start',
|
||||
block: 'start',
|
||||
});
|
||||
document
|
||||
.querySelector<HTMLElement>(`[data-id="${headingId}"]`)
|
||||
?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'start',
|
||||
block: 'start',
|
||||
});
|
||||
}}
|
||||
$radius="var(--c--globals--spacings--st)"
|
||||
$background={
|
||||
|
||||
+3
-3
@@ -91,7 +91,7 @@ export const TableContent = () => {
|
||||
$height="100%"
|
||||
$justify="center"
|
||||
$align="center"
|
||||
aria-label={t('Summary')}
|
||||
aria-label={t('Show the table of contents')}
|
||||
aria-expanded={isOpen}
|
||||
aria-controls="toc-list"
|
||||
$css={css`
|
||||
@@ -218,8 +218,8 @@ const TableContentOpened = ({
|
||||
onClick={onClose}
|
||||
$justify="center"
|
||||
$align="center"
|
||||
aria-label={t('Summary')}
|
||||
aria-expanded="true"
|
||||
aria-label={t('Hide the table of contents')}
|
||||
aria-expanded={true}
|
||||
aria-controls="toc-list"
|
||||
$css={css`
|
||||
transition: none !important;
|
||||
|
||||
@@ -96,7 +96,7 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
|
||||
const ariaLabel = docTitle;
|
||||
const isDisabled = !!doc.deleted_at;
|
||||
const actionsRef = useRef<HTMLDivElement>(null);
|
||||
const buttonOptionRef = useRef<HTMLDivElement | null>(null);
|
||||
const buttonOptionRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
const target = e.target as HTMLElement | null;
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
useMoveDoc,
|
||||
useTrans,
|
||||
} from '@/docs/doc-management';
|
||||
import { TreeSkeleton } from '@/features/skeletons/components/TreeSkeleton';
|
||||
|
||||
import { CLASS_DOC_TITLE } from '../../doc-header';
|
||||
import { KEY_DOC_TREE, useDocTree } from '../api/useDocTree';
|
||||
@@ -43,7 +44,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
treeContext?.treeData.selectedNode?.id === treeContext.root.id;
|
||||
const rootItemRef = useRef<HTMLDivElement>(null);
|
||||
const rootActionsRef = useRef<HTMLDivElement>(null);
|
||||
const rootButtonOptionRef = useRef<HTMLDivElement | null>(null);
|
||||
const rootButtonOptionRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -257,7 +258,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
|
||||
}, [currentDoc, treeContext]);
|
||||
|
||||
if (!treeContext || !treeContext.root) {
|
||||
return null;
|
||||
return <TreeSkeleton />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ type DocTreeItemActionsProps = {
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
parentId?: string | null;
|
||||
actionsRef?: React.RefObject<HTMLDivElement | null>;
|
||||
buttonOptionRef?: React.RefObject<HTMLDivElement | null>;
|
||||
buttonOptionRef?: React.RefObject<HTMLButtonElement | null>;
|
||||
};
|
||||
|
||||
export const DocTreeItemActions = ({
|
||||
@@ -48,7 +48,7 @@ export const DocTreeItemActions = ({
|
||||
}: DocTreeItemActionsProps) => {
|
||||
const internalActionsRef = useRef<HTMLDivElement | null>(null);
|
||||
const targetActionsRef = actionsRef ?? internalActionsRef;
|
||||
const internalButtonRef = useRef<HTMLDivElement | null>(null);
|
||||
const internalButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const targetButtonRef = buttonOptionRef ?? internalButtonRef;
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
+5
-2
@@ -4,9 +4,12 @@ import { useEffect, useState } from 'react';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import { Box, Text, TextErrors } from '@/components';
|
||||
import { BlockNoteReader, DocEditorContainer } from '@/docs/doc-editor/';
|
||||
import { BlockNoteReader } from '@/docs/doc-editor/components/BlockNoteEditor';
|
||||
import { DocEditorContainer } from '@/docs/doc-editor/components/DocEditor';
|
||||
import { Doc, base64ToBlocknoteXmlFragment } from '@/docs/doc-management';
|
||||
import { Versions, useDocVersion } from '@/docs/doc-versioning/';
|
||||
|
||||
import { useDocVersion } from '../api/useDocVersion';
|
||||
import { Versions } from '../types';
|
||||
|
||||
import { DocVersionHeader } from './DocVersionHeader';
|
||||
|
||||
|
||||
+23
-13
@@ -5,8 +5,8 @@ import {
|
||||
VariantType,
|
||||
useToastProvider,
|
||||
} from '@gouvfr-lasuite/cunningham-react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import {
|
||||
@@ -21,15 +21,22 @@ import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
|
||||
import { Versions } from '../types';
|
||||
import { revertUpdate } from '../utils';
|
||||
|
||||
interface ModalConfirmationVersionProps {
|
||||
onClose: () => void;
|
||||
docId: Doc['id'];
|
||||
const ModalStyle = createGlobalStyle`
|
||||
.c__modal__title {
|
||||
margin-bottom: var(--c--globals--spacings--sm);
|
||||
}
|
||||
`;
|
||||
|
||||
interface ModalConfirmationVersionProps {
|
||||
docId: Doc['id'];
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
versionId: Versions['version_id'];
|
||||
}
|
||||
|
||||
export const ModalConfirmationVersion = ({
|
||||
onClose,
|
||||
onSuccess,
|
||||
docId,
|
||||
versionId,
|
||||
}: ModalConfirmationVersionProps) => {
|
||||
@@ -39,14 +46,13 @@ export const ModalConfirmationVersion = ({
|
||||
});
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToastProvider();
|
||||
const { push } = useRouter();
|
||||
const { provider } = useProviderStore();
|
||||
const { mutate: updateDoc } = useUpdateDoc({
|
||||
listInvalidQueries: [KEY_LIST_DOC_VERSIONS],
|
||||
onSuccess: () => {
|
||||
const onDisplaySuccess = () => {
|
||||
toast(t('Version restored successfully'), VariantType.SUCCESS);
|
||||
void push(`/docs/${docId}`);
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
if (!provider || !version?.content) {
|
||||
@@ -64,6 +70,10 @@ export const ModalConfirmationVersion = ({
|
||||
},
|
||||
});
|
||||
|
||||
if (!version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
@@ -102,7 +112,7 @@ export const ModalConfirmationVersion = ({
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
size={ModalSize.SMALL}
|
||||
size={ModalSize.MEDIUM}
|
||||
title={
|
||||
<Text
|
||||
as="h1"
|
||||
@@ -111,17 +121,17 @@ export const ModalConfirmationVersion = ({
|
||||
$size="h6"
|
||||
$align="flex-start"
|
||||
>
|
||||
{t('Warning')}
|
||||
{t('Restoring an older version')}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<ModalStyle />
|
||||
<Box className="--docs--modal-confirmation-version">
|
||||
<Box>
|
||||
<Text $variation="secondary" as="p">
|
||||
{t('Your current document will revert to this version.')}
|
||||
</Text>
|
||||
<Text $variation="secondary" as="p">
|
||||
{t('If a member is editing, his works can be lost.')}
|
||||
<Text $variation="secondary" as="p" $margin="none">
|
||||
{t(
|
||||
"The current document will be replaced, but you'll still find it in the version history.",
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
+10
-4
@@ -114,11 +114,12 @@ export const ModalSelectVersion = ({
|
||||
$height="calc(100vh - 2em - 12px)"
|
||||
$css={css`
|
||||
overflow-y: hidden;
|
||||
border-left: 1px solid var(--c--globals--colors--gray-200);
|
||||
border-left: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
aria-label="version list"
|
||||
aria-label={t('Version list')}
|
||||
$css={css`
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
@@ -130,7 +131,8 @@ export const ModalSelectVersion = ({
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$css={css`
|
||||
border-bottom: 1px solid var(--c--globals--colors--gray-200);
|
||||
border-bottom: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
`}
|
||||
$padding="sm"
|
||||
>
|
||||
@@ -155,7 +157,8 @@ export const ModalSelectVersion = ({
|
||||
<Box
|
||||
$padding="xs"
|
||||
$css={css`
|
||||
border-top: 1px solid var(--c--globals--colors--gray-200);
|
||||
border-top: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
@@ -174,6 +177,9 @@ export const ModalSelectVersion = ({
|
||||
<ModalConfirmationVersion
|
||||
onClose={() => {
|
||||
restoreModal.close();
|
||||
}}
|
||||
onSuccess={() => {
|
||||
restoreModal.close();
|
||||
onClose();
|
||||
setSelectedVersionId(undefined);
|
||||
}}
|
||||
|
||||
+26
-66
@@ -1,78 +1,38 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import { BoxButton, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
|
||||
import { Versions } from '../types';
|
||||
|
||||
const ModalConfirmationVersion = dynamic(
|
||||
() =>
|
||||
import('./ModalConfirmationVersion').then((mod) => ({
|
||||
default: mod.ModalConfirmationVersion,
|
||||
})),
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
interface VersionItemProps {
|
||||
docId: Doc['id'];
|
||||
text: string;
|
||||
|
||||
versionId?: Versions['version_id'];
|
||||
isActive: boolean;
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
export const VersionItem = ({
|
||||
docId,
|
||||
versionId,
|
||||
text,
|
||||
|
||||
isActive,
|
||||
}: VersionItemProps) => {
|
||||
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
|
||||
export const VersionItem = ({ text, isActive, onSelect }: VersionItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
$width="100%"
|
||||
as="li"
|
||||
$background={isActive ? colorsTokens['gray-100'] : 'transparent'}
|
||||
$radius={spacingsTokens['3xs']}
|
||||
$css={`
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${colorsTokens['gray-100']};
|
||||
}
|
||||
`}
|
||||
$hasTransition
|
||||
$minWidth="13rem"
|
||||
className="--docs--version-item"
|
||||
>
|
||||
<Box
|
||||
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
|
||||
$align="center"
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
$width="100%"
|
||||
>
|
||||
<Box $direction="row" $gap="0.5rem" $align="center">
|
||||
<Text $weight="bold" $size="sm">
|
||||
{text}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{isModalVersionOpen && versionId && (
|
||||
<ModalConfirmationVersion
|
||||
onClose={() => setIsModalVersionOpen(false)}
|
||||
docId={docId}
|
||||
versionId={versionId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<BoxButton
|
||||
aria-label={t('Restore version of {{date}}', { date: text })}
|
||||
aria-pressed={isActive}
|
||||
$width="100%"
|
||||
$css={`
|
||||
background: ${isActive ? 'var(--c--contextuals--background--semantic--overlay--primary)' : 'transparent'};
|
||||
&:focus-visible, &:hover {
|
||||
background: var(--c--contextuals--background--semantic--overlay--primary);
|
||||
}
|
||||
`}
|
||||
className="version-item --docs--version-item"
|
||||
onClick={onSelect}
|
||||
$radius={spacingsTokens['3xs']}
|
||||
$padding={{ vertical: 'm', horizontal: 'xs' }}
|
||||
$hasTransition
|
||||
>
|
||||
<Text $weight="bold" $size="sm" $textAlign="left">
|
||||
{text}
|
||||
</Text>
|
||||
</BoxButton>
|
||||
);
|
||||
};
|
||||
|
||||
+32
-32
@@ -3,14 +3,7 @@ import { DateTime } from 'luxon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { APIError } from '@/api';
|
||||
import {
|
||||
Box,
|
||||
BoxButton,
|
||||
Icon,
|
||||
InfiniteScroll,
|
||||
Text,
|
||||
TextErrors,
|
||||
} from '@/components';
|
||||
import { Box, Icon, InfiniteScroll, Text, TextErrors } from '@/components';
|
||||
import { Doc } from '@/docs/doc-management';
|
||||
import { useDate } from '@/hooks';
|
||||
|
||||
@@ -23,7 +16,6 @@ interface VersionListStateProps {
|
||||
isLoading: boolean;
|
||||
error: APIError<unknown> | null;
|
||||
versions?: Versions[];
|
||||
doc: Doc;
|
||||
selectedVersionId?: Versions['version_id'];
|
||||
onSelectVersion?: (versionId: Versions['version_id']) => void;
|
||||
}
|
||||
@@ -31,13 +23,11 @@ interface VersionListStateProps {
|
||||
const VersionListState = ({
|
||||
onSelectVersion,
|
||||
selectedVersionId,
|
||||
|
||||
isLoading,
|
||||
error,
|
||||
versions,
|
||||
doc,
|
||||
}: VersionListStateProps) => {
|
||||
const { formatDate } = useDate();
|
||||
const { formatDateSpecial } = useDate();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -48,24 +38,23 @@ const VersionListState = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Box $gap="10px" $padding="xs">
|
||||
{versions?.map((version) => (
|
||||
<BoxButton
|
||||
aria-label="version item"
|
||||
className="version-item"
|
||||
key={version.version_id}
|
||||
onClick={() => {
|
||||
onSelectVersion?.(version.version_id);
|
||||
}}
|
||||
>
|
||||
<VersionItem
|
||||
versionId={version.version_id}
|
||||
text={formatDate(version.last_modified, DateTime.DATETIME_MED)}
|
||||
docId={doc.id}
|
||||
isActive={version.version_id === selectedVersionId}
|
||||
/>
|
||||
</BoxButton>
|
||||
))}
|
||||
<Box $gap="xxs" $padding="xs">
|
||||
{versions?.map((version) => {
|
||||
const formattedDate = formatDateSpecial(
|
||||
version.last_modified,
|
||||
'dd MMMM · HH:mm',
|
||||
);
|
||||
const isSelected = version.version_id === selectedVersionId;
|
||||
return (
|
||||
<Box as="li" key={version.version_id} $css="list-style: none;">
|
||||
<VersionItem
|
||||
text={formattedDate}
|
||||
isActive={isSelected}
|
||||
onSelect={() => onSelectVersion?.(version.version_id)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
{error && (
|
||||
<Box
|
||||
$justify="center"
|
||||
@@ -97,6 +86,7 @@ export const VersionList = ({
|
||||
selectedVersionId,
|
||||
}: VersionListProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { formatDate } = useDate();
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -112,6 +102,12 @@ export const VersionList = ({
|
||||
const versions = data?.pages.reduce((acc, page) => {
|
||||
return acc.concat(page.versions);
|
||||
}, [] as Versions[]);
|
||||
const selectedVersion = versions?.find(
|
||||
(version) => version.version_id === selectedVersionId,
|
||||
);
|
||||
const selectedVersionDate = selectedVersion
|
||||
? formatDate(selectedVersion.last_modified, DateTime.DATETIME_MED)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -127,7 +123,7 @@ export const VersionList = ({
|
||||
as="ul"
|
||||
$padding="none"
|
||||
$margin={{ top: 'none' }}
|
||||
role="listbox"
|
||||
role="list"
|
||||
>
|
||||
{versions?.length === 0 && (
|
||||
<Box $align="center" $margin="large">
|
||||
@@ -141,10 +137,14 @@ export const VersionList = ({
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
versions={versions}
|
||||
doc={doc}
|
||||
selectedVersionId={selectedVersionId}
|
||||
/>
|
||||
</InfiniteScroll>
|
||||
<Text className="sr-only" aria-live="polite">
|
||||
{selectedVersionDate
|
||||
? t('Selected version {{date}}', { date: selectedVersionDate })
|
||||
: ''}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Doc } from '../doc-management';
|
||||
import { Doc } from '../doc-management/types';
|
||||
|
||||
export interface APIListVersions {
|
||||
count: number;
|
||||
|
||||
@@ -116,7 +116,9 @@ export const DocsGrid = ({
|
||||
$padding={{
|
||||
bottom: 'md',
|
||||
}}
|
||||
{...(withUpload ? getRootProps({ className: 'dropzone' }) : {})}
|
||||
{...(withUpload
|
||||
? getRootProps({ className: 'dropzone', tabIndex: -1 })
|
||||
: {})}
|
||||
>
|
||||
{withUpload && <input {...getInputProps()} />}
|
||||
<DocGridTitleBar
|
||||
|
||||
+10
-11
@@ -19,7 +19,7 @@ import {
|
||||
useDuplicateDoc,
|
||||
useTrans,
|
||||
} from '@/docs/doc-management';
|
||||
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
||||
import { focusMainContentStart } from '@/layouts/utils';
|
||||
import { useFocusStore } from '@/stores';
|
||||
|
||||
import { DocMoveModal } from './DocMoveModal';
|
||||
@@ -55,10 +55,9 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
|
||||
|
||||
const { mutate: duplicateDoc } = useDuplicateDoc({
|
||||
onSuccess: () => {
|
||||
const mainContent = document.getElementById(MAIN_LAYOUT_ID);
|
||||
if (mainContent) {
|
||||
requestAnimationFrame(() => mainContent.focus());
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
focusMainContentStart({ preventScroll: true });
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -73,9 +72,9 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
|
||||
{
|
||||
label: doc.is_favorite ? t('Unpin') : t('Pin'),
|
||||
icon: doc.is_favorite ? (
|
||||
<KeepOffSVG width={24} height={24} />
|
||||
<KeepOffSVG width={24} height={24} aria-hidden="true" />
|
||||
) : (
|
||||
<KeepSVG width={24} height={24} />
|
||||
<KeepSVG width={24} height={24} aria-hidden="true" />
|
||||
),
|
||||
callback: () => {
|
||||
if (doc.is_favorite) {
|
||||
@@ -89,7 +88,7 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Share'),
|
||||
icon: <GroupSVG width={24} height={24} />,
|
||||
icon: <GroupSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => {
|
||||
shareModal.open();
|
||||
},
|
||||
@@ -98,7 +97,7 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Move into a doc'),
|
||||
icon: <DocMoveInSVG width={24} height={24} />,
|
||||
icon: <DocMoveInSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => {
|
||||
importModal.open();
|
||||
},
|
||||
@@ -107,7 +106,7 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Duplicate'),
|
||||
icon: <ContentCopySVG width={24} height={24} />,
|
||||
icon: <ContentCopySVG width={24} height={24} aria-hidden="true" />,
|
||||
disabled: !doc.abilities.duplicate,
|
||||
callback: () => {
|
||||
duplicateDoc({
|
||||
@@ -120,7 +119,7 @@ export const DocsGridActions = ({ doc }: DocsGridActionsProps) => {
|
||||
},
|
||||
{
|
||||
label: t('Delete'),
|
||||
icon: <DeleteSVG width={24} height={24} />,
|
||||
icon: <DeleteSVG width={24} height={24} aria-hidden="true" />,
|
||||
callback: () => deleteModal.open(),
|
||||
disabled: !doc.abilities.destroy,
|
||||
testId: `docs-grid-actions-remove-${doc.id}`,
|
||||
|
||||
+7
-3
@@ -1,4 +1,4 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import fetchMock from 'fetch-mock';
|
||||
import i18next from 'i18next';
|
||||
import { DateTime } from 'luxon';
|
||||
@@ -73,7 +73,9 @@ describe('DocsGridItemDate', () => {
|
||||
});
|
||||
|
||||
it(`should render rendered the updated_at field in the correct language`, async () => {
|
||||
await i18next.changeLanguage('fr');
|
||||
await act(async () => {
|
||||
await i18next.changeLanguage('fr');
|
||||
});
|
||||
|
||||
render(
|
||||
<DocsGridItemDate
|
||||
@@ -90,7 +92,9 @@ describe('DocsGridItemDate', () => {
|
||||
|
||||
expect(screen.getByText('il y a 5 jours')).toBeInTheDocument();
|
||||
|
||||
await i18next.changeLanguage('en');
|
||||
await act(async () => {
|
||||
await i18next.changeLanguage('en');
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
|
||||
@@ -60,7 +60,7 @@ export const HelpMenu = ({
|
||||
>
|
||||
<Box $direction="row" $align="center">
|
||||
<Button
|
||||
aria-label={t('Open onboarding menu')}
|
||||
aria-label={t('Open help menu')}
|
||||
color={colorButton || 'neutral'}
|
||||
variant="tertiary"
|
||||
iconPosition="left"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { announce } from '@react-aria/live-announcer';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
@@ -17,23 +18,35 @@ export const LanguagePicker = () => {
|
||||
const { changeLanguageSynchronized } = useSynchronizedLanguage();
|
||||
const language = i18n.language;
|
||||
|
||||
const toLangTag = (locale: string) => locale.replace('_', '-');
|
||||
|
||||
// Compute options for dropdown
|
||||
const optionsPicker = useMemo(() => {
|
||||
const backendOptions = conf?.LANGUAGES ?? [[language, language]];
|
||||
return backendOptions.map(([backendLocale, backendLabel]) => {
|
||||
return {
|
||||
label: backendLabel,
|
||||
lang: toLangTag(backendLocale),
|
||||
value: backendLocale,
|
||||
isSelected: getMatchingLocales([backendLocale], [language]).length > 0,
|
||||
callback: () => changeLanguageSynchronized(backendLocale, user),
|
||||
callback: async () => {
|
||||
await changeLanguageSynchronized(backendLocale, user);
|
||||
announce(
|
||||
t('Language changed to {{language}}', {
|
||||
language: backendLabel,
|
||||
defaultValue: `Language changed to ${backendLabel}`,
|
||||
}),
|
||||
'polite',
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [changeLanguageSynchronized, conf?.LANGUAGES, language, user]);
|
||||
}, [changeLanguageSynchronized, conf?.LANGUAGES, language, t, user]);
|
||||
|
||||
// Extract current language label for display
|
||||
const currentLanguageLabel =
|
||||
conf?.LANGUAGES.find(
|
||||
([code]) => getMatchingLocales([code], [language]).length > 0,
|
||||
)?.[1] || language;
|
||||
const [currentLanguageCode, currentLanguageLabel] = conf?.LANGUAGES.find(
|
||||
([code]) => getMatchingLocales([code], [language]).length > 0,
|
||||
) ?? [language, language];
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
@@ -65,7 +78,9 @@ export const LanguagePicker = () => {
|
||||
$align="center"
|
||||
>
|
||||
<Icon iconName="translate" $color="inherit" $size="xl" />
|
||||
{currentLanguageLabel}
|
||||
<span lang={toLangTag(currentLanguageCode)}>
|
||||
{currentLanguageLabel}
|
||||
</span>
|
||||
</Box>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createGlobalStyle, css } from 'styled-components';
|
||||
|
||||
@@ -23,11 +25,15 @@ const MobileLeftPanelStyle = createGlobalStyle`
|
||||
|
||||
export const LeftPanel = () => {
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { t } = useTranslation();
|
||||
if (isDesktop) {
|
||||
return <LeftPanelDesktop />;
|
||||
}
|
||||
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { isPanelOpen, isPanelOpenMobile } = useLeftPanelStore();
|
||||
const isPanelOpenState = isDesktop ? isPanelOpen : isPanelOpenMobile;
|
||||
return <LeftPanelMobile />;
|
||||
};
|
||||
|
||||
export const LeftPanelDesktop = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data: config } = useConfig();
|
||||
/**
|
||||
* The onboarding can be disable, so we need to check if it's enabled before displaying the help menu.
|
||||
@@ -36,42 +42,51 @@ export const LeftPanel = () => {
|
||||
*/
|
||||
const showHelpMenu = config?.theme_customization?.onboarding?.enabled;
|
||||
|
||||
if (isDesktop) {
|
||||
return (
|
||||
return (
|
||||
<Box
|
||||
data-testid="left-panel-desktop"
|
||||
$css={css`
|
||||
height: calc(100vh - ${HEADER_HEIGHT}px);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--c--contextuals--background--surface--primary);
|
||||
`}
|
||||
className="--docs--left-panel-desktop"
|
||||
as="nav"
|
||||
aria-label={t('Document sections')}
|
||||
>
|
||||
<Box
|
||||
data-testid="left-panel-desktop"
|
||||
$css={css`
|
||||
height: calc(100vh - ${HEADER_HEIGHT}px);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--c--contextuals--background--surface--primary);
|
||||
flex: 0 0 auto;
|
||||
`}
|
||||
className="--docs--left-panel-desktop"
|
||||
as="nav"
|
||||
aria-label={t('Document sections')}
|
||||
>
|
||||
<Box
|
||||
$css={css`
|
||||
flex: 0 0 auto;
|
||||
`}
|
||||
>
|
||||
<LeftPanelHeader />
|
||||
</Box>
|
||||
<LeftPanelContent />
|
||||
{showHelpMenu && (
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<Box $padding={{ horizontal: 'sm' }} $justify="flex-start">
|
||||
<HelpMenu />
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
)}
|
||||
<LeftPanelHeader />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
<LeftPanelContent />
|
||||
{showHelpMenu && (
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<Box $padding={{ horizontal: 'sm' }} $justify="flex-start">
|
||||
<HelpMenu />
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const LeftPanelMobile = () => {
|
||||
const { t } = useTranslation();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { closePanel, isPanelOpenMobile } = useLeftPanelStore();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
closePanel({ type: 'mobile' });
|
||||
}, [pathname, closePanel]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isPanelOpenState && <MobileLeftPanelStyle />}
|
||||
{isPanelOpenMobile && <MobileLeftPanelStyle />}
|
||||
<Box
|
||||
$hasTransition
|
||||
$height="100vh"
|
||||
@@ -81,7 +96,7 @@ export const LeftPanel = () => {
|
||||
height: calc(100dvh - 52px);
|
||||
border-right: 1px solid var(--c--globals--colors--gray-200);
|
||||
position: fixed;
|
||||
transform: translateX(${isPanelOpenState ? '0' : '-100dvw'});
|
||||
transform: translateX(${isPanelOpenMobile ? '0' : '-100dvw'});
|
||||
background-color: var(--c--contextuals--background--surface--primary);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
+39
-1
@@ -18,8 +18,46 @@ export const LeftPanelCollapseButton = () => {
|
||||
const { isPanelOpen, togglePanel } = useLeftPanelStore();
|
||||
const { currentDoc } = useDocStore();
|
||||
const [isDocTitleVisible, setIsDocTitleVisible] = useState(true);
|
||||
const [isDocTitleInDom, setIsDocTitleInDom] = useState(true);
|
||||
|
||||
/**
|
||||
* CLASS_DOC_TITLE is not every time in the DOM when
|
||||
* this component is rendered, we need to observe the DOM
|
||||
* to know when it is added, then we can observe
|
||||
* its visibility.
|
||||
*/
|
||||
useEffect(() => {
|
||||
setIsDocTitleInDom(false);
|
||||
|
||||
const docTitleEl = document.querySelector(`.${CLASS_DOC_TITLE}`);
|
||||
if (docTitleEl) {
|
||||
setIsDocTitleInDom(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const mutationObserver = new MutationObserver(() => {
|
||||
if (document.querySelector(`.${CLASS_DOC_TITLE}`)) {
|
||||
mutationObserver.disconnect();
|
||||
setIsDocTitleInDom(true);
|
||||
}
|
||||
});
|
||||
|
||||
mutationObserver.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
return () => {
|
||||
mutationObserver.disconnect();
|
||||
};
|
||||
}, [currentDoc?.id]);
|
||||
|
||||
/**
|
||||
* When the doc title is in the DOM, we observe its
|
||||
* visibility to show or hide the collapse button accordingly
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!isDocTitleInDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainContent = document.getElementById(MAIN_LAYOUT_ID);
|
||||
const docTitleEl = document.querySelector(`.${CLASS_DOC_TITLE}`);
|
||||
|
||||
@@ -43,7 +81,7 @@ export const LeftPanelCollapseButton = () => {
|
||||
observer.disconnect();
|
||||
setIsDocTitleVisible(true);
|
||||
};
|
||||
}, [currentDoc?.id]);
|
||||
}, [isDocTitleInDom]);
|
||||
|
||||
const { untitledDocument } = useTrans();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user