From 44654d353b3e50487364067d6121244dbc67ed77 Mon Sep 17 00:00:00 2001 From: Sylvain Boissel Date: Wed, 14 Jan 2026 15:01:57 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8(backend)=20sort=20user=20search=20?= =?UTF-8?q?results=20by=20proxmity=20with=20the=20active=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows a user to find more easily the other users they search, with the following order of priority: - users they already share documents with (more recent first) - users that share the same full email domain - users that share the same partial email domain (last two parts) - other users --- CHANGELOG.md | 28 ++-- renovate.json | 7 + src/backend/core/api/viewsets.py | 75 ++++++++++- src/backend/core/tests/test_api_users.py | 79 +++++++++++- src/backend/core/tests/test_utils.py | 15 +++ ...test_utils_users_sharing_documents_with.py | 60 +++++++++ src/backend/core/utils.py | 42 +++++- .../locale/br_FR/LC_MESSAGES/django.po | 6 +- .../locale/de_DE/LC_MESSAGES/django.po | 6 +- .../locale/en_US/LC_MESSAGES/django.po | 6 +- .../locale/es_ES/LC_MESSAGES/django.po | 6 +- .../locale/fr_FR/LC_MESSAGES/django.po | 6 +- .../locale/it_IT/LC_MESSAGES/django.po | 6 +- .../locale/nl_NL/LC_MESSAGES/django.po | 6 +- .../locale/pt_PT/LC_MESSAGES/django.po | 6 +- .../locale/ru_RU/LC_MESSAGES/django.po | 6 +- .../locale/sl_SI/LC_MESSAGES/django.po | 6 +- .../locale/sv_SE/LC_MESSAGES/django.po | 6 +- .../locale/tr_TR/LC_MESSAGES/django.po | 6 +- .../locale/uk_UA/LC_MESSAGES/django.po | 6 +- .../locale/zh_CN/LC_MESSAGES/django.po | 6 +- src/backend/pyproject.toml | 4 +- .../app-impress/doc-comments.spec.ts | 2 +- .../app-impress/doc-inherited-share.spec.ts | 2 +- .../e2e/__tests__/app-impress/utils-common.ts | 4 +- src/frontend/apps/e2e/package.json | 2 +- src/frontend/apps/impress/package.json | 3 +- .../InterlinkingLinkInlineContent.tsx | 121 +++++++++++++----- .../Interlinking/SearchPage.tsx | 1 - .../interlinkingLinkDocx.tsx | 12 +- .../interlinkingLinkODT.tsx | 12 +- .../interlinkingLinkPDF.tsx | 14 +- .../apps/impress/src/i18n/translations.json | 15 ++- src/frontend/package.json | 2 +- .../packages/eslint-plugin-docs/package.json | 2 +- src/frontend/packages/i18n/package.json | 2 +- src/frontend/servers/y-provider/package.json | 2 +- src/helm/helmfile.yaml.gotmpl | 4 +- src/helm/impress/Chart.yaml | 2 +- src/mail/package.json | 2 +- 40 files changed, 478 insertions(+), 120 deletions(-) create mode 100644 src/backend/core/tests/test_utils_users_sharing_documents_with.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e409e3..d0a25024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,29 @@ and this project adheres to ## [Unreleased] +### Fixed + +- ✅(e2e) fix e2e test for other browsers #1799 + +### Changed + +- 🚸(backend) sort user search results by proximity with the active user #1802 + +## [4.4.0] - 2026-01-13 + ### Added - ✨(backend) add documents/all endpoint with descendants #1553 - ✅(export) add PDF regression tests #1762 - 📝(docs) Add language configuration documentation #1757 - 🔒(helm) Set default security context #1750 -- ✨(backend) use langfuse to monitor AI actions +- ✨(backend) use langfuse to monitor AI actions #1776 + +### Changed + +- ♿(frontend) improve accessibility: + - ♿(frontend) make html export accessible to screen reader users #1743 + - ♿(frontend) add missing label and fix Axes errors to improve a11y #1693 ### Fixed @@ -24,12 +40,7 @@ and this project adheres to ### Security - 🔒️(backend) validate more strictly url used by cors-proxy endpoint #1768 - -### Changed - -- ♿(frontend) improve accessibility: - - ♿(frontend) make html export accessible to screen reader users #1743 - - ♿(frontend) add missing label and fix Axes errors to improve a11y #1693 +- 🔒️(frontend) fix props vulnerability in Interlinking #1792 ## [4.3.0] - 2026-01-05 @@ -987,7 +998,8 @@ 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.3.0...main +[unreleased]: https://github.com/suitenumerique/docs/compare/v4.4.0...main +[v4.4.0]: https://github.com/suitenumerique/docs/releases/v4.4.0 [v4.3.0]: https://github.com/suitenumerique/docs/releases/v4.3.0 [v4.2.0]: https://github.com/suitenumerique/docs/releases/v4.2.0 [v4.1.0]: https://github.com/suitenumerique/docs/releases/v4.1.0 diff --git a/renovate.json b/renovate.json index 250dee8e..7f8e9cab 100644 --- a/renovate.json +++ b/renovate.json @@ -31,6 +31,13 @@ "matchPackageNames": ["django"], "allowedVersions": "<6.0.0" }, + { + + "groupName": "allowed celery versions", + "matchManagers": ["pep621"], + "matchPackageNames": ["celery"], + "allowedVersions": "<5.6.0" + }, { "enabled": false, "groupName": "ignored js dependencies", diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index c4a137ee..a9cc5a7c 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -59,7 +59,12 @@ from core.services.search_indexers import ( get_visited_document_ids_of, ) from core.tasks.mail import send_ask_for_access_mail -from core.utils import extract_attachments, filter_descendants +from core.utils import ( + extract_attachments, + extract_email_domain_parts, + filter_descendants, + users_sharing_documents_with, +) from . import permissions, serializers, utils from .filters import DocumentFilter, ListDocumentFilter, UserSearchFilter @@ -218,18 +223,77 @@ class UserViewSet( # Use trigram similarity for non-email-like queries # For performance reasons we filter first by similarity, which relies on an - # index, then only calculate precise similarity scores for sorting purposes + # index, then only calculate precise similarity scores for sorting purposes. + # + # Additionally we reorder results to prefer users "closer" to the current + # user: users they recently shared documents with, same full domain, same + # partial domain (e.g. both end with "gouv.fr"). To achieve that without + # complex SQL we build a proximity score in Python and returnthe top N results. + current_user = self.request.user + shared_map = users_sharing_documents_with(current_user) - return ( + user_full_domain, user_partial_domain = extract_email_domain_parts( + current_user.email or "" + ) + + candidates = list( queryset.annotate( sim_email=TrigramSimilarity("email", query), sim_name=TrigramSimilarity("full_name", query), ) .annotate(similarity=Greatest("sim_email", "sim_name")) .filter(similarity__gt=0.2) - .order_by("-similarity")[: settings.API_USERS_LIST_LIMIT] + .order_by("-similarity") ) + # Build ordering key for each candidate + def _sort_key(u): + # shared priority: most recent first + # Use shared_last_at timestamp numeric for secondary ordering when shared. + shared_last_at = shared_map.get(u.id) + if shared_last_at: + is_shared = 1 + shared_score = int(shared_last_at.timestamp()) + else: + is_shared = 0 + shared_score = 0 + + # domain proximity + candidate_full_domain, candidate_partial_domain = ( + extract_email_domain_parts(u.email or "") + ) + + same_full_domain = ( + 1 + if candidate_full_domain and candidate_full_domain == user_full_domain + else 0 + ) + + same_partial_domain = ( + 1 + if candidate_partial_domain + and candidate_partial_domain == user_partial_domain + else 0 + ) + + # similarity fallback + sim = getattr(u, "similarity", 0) or 0 + + return ( + is_shared, + shared_score, + same_full_domain, + same_partial_domain, + sim, + ) + + # Sort candidates by the key descending and return top N as a queryset-like + # list. Keep return type consistent with previous behavior (QuerySet slice + # was returned) by returning a list of model instances. + candidates.sort(key=_sort_key, reverse=True) + + return candidates[: settings.API_USERS_LIST_LIMIT] + @drf.decorators.action( detail=False, methods=["get"], @@ -2231,8 +2295,7 @@ class InvitationViewset( ) # Abilities are computed based on logged-in user's role and # the user role on each document access - .annotate(user_roles=db.Subquery(user_roles_query)) - .distinct() + .annotate(user_roles=db.Subquery(user_roles_query)).distinct() ) return queryset diff --git a/src/backend/core/tests/test_api_users.py b/src/backend/core/tests/test_api_users.py index f2a30e93..86437703 100644 --- a/src/backend/core/tests/test_api_users.py +++ b/src/backend/core/tests/test_api_users.py @@ -2,6 +2,8 @@ Test users API endpoints in the impress core app. """ +from django.utils import timezone + import pytest from rest_framework.test import APIClient @@ -201,10 +203,85 @@ def test_api_users_list_query_accented_full_name(): assert users == [] +def test_api_users_list_sorted_by_closest_match(): + """ + Authenticated users should be able to list users and the results should be + sorted by closest match to the query. + + Sorting criteria are : + - Shared documents with the user (most recent first) + - Same full email domain (example.gouv.fr) + - Same partial email domain (gouv.fr) + + Case in point: the logged-in user has recently shared documents + with pierre.dupont@beta.gouv.fr and less recently with pierre.durand@impots.gouv.fr. + + Other users named Pierre also exist: + - pierre.thomas@example.com + - pierre.petit@anct.gouv.fr + - pierre.robert@culture.gouv.fr + + The search results should be ordered as follows: + + # Shared with first + - pierre.dupond@beta.gouv.fr # Most recent first + - pierre.durand@impots.gouv.fr + # Same full domain second + - pierre.petit@anct.gouv.fr + # Same partial domain third + - pierre.robert@culture.gouv.fr + # Others last + - paul.thomas@example.com + """ + + user = factories.UserFactory( + email="martin.bernard@anct.gouv.fr", full_name="Martin Bernard" + ) + + client = APIClient() + client.force_login(user) + + pierre_1 = factories.UserFactory(email="pierre.dupont@beta.gouv.fr") + pierre_2 = factories.UserFactory(email="pierre.durand@impots.gouv.fr") + pierre_3 = factories.UserFactory(email="pierre.thomas@example.com") + pierre_4 = factories.UserFactory(email="pierre.petit@anct.gouv.fr") + pierre_5 = factories.UserFactory(email="pierre.robert@culture.gouv.fr") + + document_1 = factories.DocumentFactory(creator=user) + document_2 = factories.DocumentFactory(creator=user) + factories.UserDocumentAccessFactory(user=user, document=document_1) + factories.UserDocumentAccessFactory(user=user, document=document_2) + + now = timezone.now() + last_week = now - timezone.timedelta(days=7) + last_month = now - timezone.timedelta(days=30) + + # The factory cannot set the created_at directly, so we force it after creation + p1_d1 = factories.UserDocumentAccessFactory(user=pierre_1, document=document_1) + p1_d1.created_at = last_week + p1_d1.save() + + p2_d2 = factories.UserDocumentAccessFactory(user=pierre_2, document=document_2) + p2_d2.created_at = last_month + p2_d2.save() + + response = client.get("/api/v1.0/users/?q=Pierre") + assert response.status_code == 200 + user_ids = [user["email"] for user in response.json()] + + assert user_ids == [ + str(pierre_1.email), + str(pierre_2.email), + str(pierre_4.email), + str(pierre_5.email), + str(pierre_3.email), + ] + + def test_api_users_list_limit(settings): """ Authenticated users should be able to list users and the number of results - should be limited to 10. + should be limited to API_USERS_LIST_LIMIT (by default 5). """ user = factories.UserFactory() diff --git a/src/backend/core/tests/test_utils.py b/src/backend/core/tests/test_utils.py index 42d588c5..820ac3d1 100644 --- a/src/backend/core/tests/test_utils.py +++ b/src/backend/core/tests/test_utils.py @@ -100,3 +100,18 @@ def test_utils_get_ancestor_to_descendants_map_multiple_paths(): "000100020005": {"000100020005"}, "00010003": {"00010003"}, } + + +def test_utils_extract_email_domain_parts_when_email_is_valid(): + """Test extraction of email domain parts.""" + email = "firstname.lastname@numerique.gouv.fr" + full_domain, partial_domain = utils.extract_email_domain_parts(email) + assert full_domain == "numerique.gouv.fr" + assert partial_domain == "gouv.fr" + + +def test_utils_extract_email_domain_parts_when_email_is_empty(): + empty_email = "" + full_domain, partial_domain = utils.extract_email_domain_parts(empty_email) + assert full_domain == "" + assert partial_domain == "" diff --git a/src/backend/core/tests/test_utils_users_sharing_documents_with.py b/src/backend/core/tests/test_utils_users_sharing_documents_with.py new file mode 100644 index 00000000..38635aa2 --- /dev/null +++ b/src/backend/core/tests/test_utils_users_sharing_documents_with.py @@ -0,0 +1,60 @@ +from django.utils import timezone + +import pytest + +from core import factories, utils + +pytestmark = pytest.mark.django_db + + +def test_utils_users_sharing_documents_with(): + """Test users_sharing_documents_with function.""" + + user = factories.UserFactory( + email="martin.bernard@anct.gouv.fr", full_name="Martin Bernard" + ) + + pierre_1 = factories.UserFactory( + email="pierre.dupont@beta.gouv.fr", full_name="Pierre Dupont" + ) + pierre_2 = factories.UserFactory( + email="pierre.durand@impots.gouv.fr", full_name="Pierre Durand" + ) + + now = timezone.now() + yesterday = now - timezone.timedelta(days=1) + last_week = now - timezone.timedelta(days=7) + last_month = now - timezone.timedelta(days=30) + + document_1 = factories.DocumentFactory(creator=user) + document_2 = factories.DocumentFactory(creator=user) + document_3 = factories.DocumentFactory(creator=user) + + factories.UserDocumentAccessFactory(user=user, document=document_1) + factories.UserDocumentAccessFactory(user=user, document=document_2) + factories.UserDocumentAccessFactory(user=user, document=document_3) + + # The factory cannot set the created_at directly, so we force it after creation + doc_1_pierre_1 = factories.UserDocumentAccessFactory( + user=pierre_1, document=document_1, created_at=last_week + ) + doc_1_pierre_1.created_at = last_week + doc_1_pierre_1.save() + doc_2_pierre_2 = factories.UserDocumentAccessFactory( + user=pierre_2, document=document_2 + ) + doc_2_pierre_2.created_at = last_month + doc_2_pierre_2.save() + + doc_3_pierre_2 = factories.UserDocumentAccessFactory( + user=pierre_2, document=document_3 + ) + doc_3_pierre_2.created_at = yesterday + doc_3_pierre_2.save() + + shared_map = utils.users_sharing_documents_with(user) + + assert shared_map == { + pierre_1.id: last_week, + pierre_2.id: yesterday, + } diff --git a/src/backend/core/utils.py b/src/backend/core/utils.py index 357ede03..32f452e4 100644 --- a/src/backend/core/utils.py +++ b/src/backend/core/utils.py @@ -4,10 +4,14 @@ import base64 import re from collections import defaultdict +from django.core.exceptions import ValidationError +from django.core.validators import validate_email +from django.db import models as db + import pycrdt from bs4 import BeautifulSoup -from core import enums +from core import enums, models def get_ancestor_to_descendants_map(paths, steplen): @@ -96,3 +100,39 @@ def extract_attachments(content): xml_content = base64_yjs_to_xml(content) return re.findall(enums.MEDIA_STORAGE_URL_EXTRACT, xml_content) + + +def users_sharing_documents_with(user): + """ + Returns a map of users sharing documents with the given user, + sorted by last shared date. + """ + + user_docs_qs = models.DocumentAccess.objects.filter(user=user).values_list( + "document_id", flat=True + ) + shared_qs = ( + models.DocumentAccess.objects.filter(document_id__in=user_docs_qs) + .exclude(user=user) + .values("user") + .annotate(last_shared=db.Max("created_at")) + ) + return {item["user"]: item["last_shared"] for item in shared_qs} + + +def extract_email_domain_parts(email): + """ + Extracts the full domain and partial domain from an email address as a tuple. + The partial domain consists of the last two segments of the domain, eg. "gouv.fr". + + If the email is invalid (eg, is an empty string), returns empty strings. + """ + try: + validate_email(email) + except ValidationError: + return "", "" + + domain = email.split("@", 1)[1].lower() + parts = domain.split(".") + partial_domain = ".".join(parts[-2:]) if len(parts) >= 2 else domain + return domain, partial_domain diff --git a/src/backend/locale/br_FR/LC_MESSAGES/django.po b/src/backend/locale/br_FR/LC_MESSAGES/django.po index 3a946232..1c321e74 100644 --- a/src/backend/locale/br_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/br_FR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Breton\n" "Language: br_FR\n" @@ -79,7 +79,7 @@ msgstr "Doare korf" msgid "Format" msgstr "Stumm" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "eilenn {title}" diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po index 8edff316..7215f3d1 100644 --- a/src/backend/locale/de_DE/LC_MESSAGES/django.po +++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: German\n" "Language: de_DE\n" @@ -79,7 +79,7 @@ msgstr "Typ" msgid "Format" msgstr "Format" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "Kopie von {title}" diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po index 8ff076d1..90f6c72c 100644 --- a/src/backend/locale/en_US/LC_MESSAGES/django.po +++ b/src/backend/locale/en_US/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: English\n" "Language: en_US\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/es_ES/LC_MESSAGES/django.po b/src/backend/locale/es_ES/LC_MESSAGES/django.po index 828a1b41..c4f38cad 100644 --- a/src/backend/locale/es_ES/LC_MESSAGES/django.po +++ b/src/backend/locale/es_ES/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Spanish\n" "Language: es_ES\n" @@ -79,7 +79,7 @@ msgstr "Tipo de Cuerpo" msgid "Format" msgstr "Formato" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "copia de {title}" diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po index af9e20d6..a0d47f41 100644 --- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" @@ -79,7 +79,7 @@ msgstr "Type de corps" msgid "Format" msgstr "Format" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "copie de {title}" diff --git a/src/backend/locale/it_IT/LC_MESSAGES/django.po b/src/backend/locale/it_IT/LC_MESSAGES/django.po index 47dd1dba..10f4a41a 100644 --- a/src/backend/locale/it_IT/LC_MESSAGES/django.po +++ b/src/backend/locale/it_IT/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Italian\n" "Language: it_IT\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "Formato" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "copia di {title}" diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po index d1ebbae3..b52b0a61 100644 --- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po +++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Dutch\n" "Language: nl_NL\n" @@ -79,7 +79,7 @@ msgstr "Text type" msgid "Format" msgstr "Formaat" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "kopie van {title}" diff --git a/src/backend/locale/pt_PT/LC_MESSAGES/django.po b/src/backend/locale/pt_PT/LC_MESSAGES/django.po index fd65edc6..8a997815 100644 --- a/src/backend/locale/pt_PT/LC_MESSAGES/django.po +++ b/src/backend/locale/pt_PT/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Portuguese\n" "Language: pt_PT\n" @@ -79,7 +79,7 @@ msgstr "Tipo de corpo" msgid "Format" msgstr "Formato" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "cópia de {title}" diff --git a/src/backend/locale/ru_RU/LC_MESSAGES/django.po b/src/backend/locale/ru_RU/LC_MESSAGES/django.po index eb5a1c21..8853268c 100644 --- a/src/backend/locale/ru_RU/LC_MESSAGES/django.po +++ b/src/backend/locale/ru_RU/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Russian\n" "Language: ru_RU\n" @@ -79,7 +79,7 @@ msgstr "Тип сообщения" msgid "Format" msgstr "Формат" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "копия {title}" diff --git a/src/backend/locale/sl_SI/LC_MESSAGES/django.po b/src/backend/locale/sl_SI/LC_MESSAGES/django.po index 5ee111c6..a7e7e2b6 100644 --- a/src/backend/locale/sl_SI/LC_MESSAGES/django.po +++ b/src/backend/locale/sl_SI/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Slovenian\n" "Language: sl_SI\n" @@ -79,7 +79,7 @@ msgstr "Vrsta telesa" msgid "Format" msgstr "Oblika" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/sv_SE/LC_MESSAGES/django.po b/src/backend/locale/sv_SE/LC_MESSAGES/django.po index 51d8cc6f..b6c7a980 100644 --- a/src/backend/locale/sv_SE/LC_MESSAGES/django.po +++ b/src/backend/locale/sv_SE/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Swedish\n" "Language: sv_SE\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "Format" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/tr_TR/LC_MESSAGES/django.po b/src/backend/locale/tr_TR/LC_MESSAGES/django.po index cdf4bd5d..1e179aee 100644 --- a/src/backend/locale/tr_TR/LC_MESSAGES/django.po +++ b/src/backend/locale/tr_TR/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Turkish\n" "Language: tr_TR\n" @@ -79,7 +79,7 @@ msgstr "" msgid "Format" msgstr "" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "" diff --git a/src/backend/locale/uk_UA/LC_MESSAGES/django.po b/src/backend/locale/uk_UA/LC_MESSAGES/django.po index 20128a23..b2c08c8d 100644 --- a/src/backend/locale/uk_UA/LC_MESSAGES/django.po +++ b/src/backend/locale/uk_UA/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Language: uk_UA\n" @@ -79,7 +79,7 @@ msgstr "Тип вмісту" msgid "Format" msgstr "Формат" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "копія {title}" diff --git a/src/backend/locale/zh_CN/LC_MESSAGES/django.po b/src/backend/locale/zh_CN/LC_MESSAGES/django.po index 7e140f51..d5190b5d 100644 --- a/src/backend/locale/zh_CN/LC_MESSAGES/django.po +++ b/src/backend/locale/zh_CN/LC_MESSAGES/django.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: lasuite-docs\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-16 21:44+0000\n" -"PO-Revision-Date: 2026-01-05 08:21\n" +"POT-Creation-Date: 2026-01-08 15:38+0000\n" +"PO-Revision-Date: 2026-01-13 13:17\n" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" @@ -79,7 +79,7 @@ msgstr "正文类型" msgid "Format" msgstr "格式" -#: build/lib/core/api/viewsets.py:1024 core/api/viewsets.py:1024 +#: build/lib/core/api/viewsets.py:1081 core/api/viewsets.py:1081 #, python-brace-format msgid "copy of {title}" msgstr "{title} 的副本" diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index f58d055b..d532906d 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "impress" -version = "4.3.0" +version = "4.4.0" authors = [{ "name" = "DINUM", "email" = "dev@mail.numerique.gouv.fr" }] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -28,7 +28,7 @@ dependencies = [ "beautifulsoup4==4.14.3", "boto3==1.42.17", "Brotli==1.2.0", - "celery[redis]==5.6.0", + "celery[redis]==5.5.3", "django-configurations==2.5.1", "django-cors-headers==4.9.0", "django-countries==8.2.0", diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts index 665a9f7a..b73814b4 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts @@ -58,7 +58,7 @@ test.describe('Doc Comments', () => { await page.getByRole('button', { name: '👍' }).click(); await expect( - thread.getByRole('img', { name: 'E2E Chromium' }).first(), + thread.getByRole('img', { name: `E2E ${browserName}` }).first(), ).toBeVisible(); await expect(thread.getByText('This is a comment').first()).toBeVisible(); await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts index efc19da6..7ad02d31 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-inherited-share.spec.ts @@ -21,7 +21,7 @@ test.describe('Inherited share accesses', () => { `doc-share-member-row-user.test@${browserName}.test`, ); await expect(user).toBeVisible(); - await expect(user.getByText('E2E Chromium')).toBeVisible(); + await expect(user.getByText(`E2E ${browserName}`)).toBeVisible(); await expect(user.getByText('Owner')).toBeVisible(); await page diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts index 67adf44c..da4a722d 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts @@ -224,7 +224,9 @@ export const updateDocTitle = async (page: Page, title: string) => { await expect(input).toHaveText(''); await expect(input).toBeVisible(); await input.click(); - await input.fill(title); + await input.fill(title, { + force: true, + }); await input.click(); await input.blur(); await verifyDocName(page, title); diff --git a/src/frontend/apps/e2e/package.json b/src/frontend/apps/e2e/package.json index 86e3a26b..7328a51b 100644 --- a/src/frontend/apps/e2e/package.json +++ b/src/frontend/apps/e2e/package.json @@ -1,6 +1,6 @@ { "name": "app-e2e", - "version": "4.3.0", + "version": "4.4.0", "repository": "https://github.com/suitenumerique/docs", "author": "DINUM", "license": "MIT", diff --git a/src/frontend/apps/impress/package.json b/src/frontend/apps/impress/package.json index cb701fc9..71646706 100644 --- a/src/frontend/apps/impress/package.json +++ b/src/frontend/apps/impress/package.json @@ -1,6 +1,6 @@ { "name": "app-impress", - "version": "4.3.0", + "version": "4.4.0", "repository": "https://github.com/suitenumerique/docs", "author": "DINUM", "license": "MIT", @@ -68,6 +68,7 @@ "react-select": "5.10.2", "styled-components": "6.1.19", "use-debounce": "10.0.6", + "uuid": "13.0.0", "y-protocols": "1.0.7", "yjs": "*", "zustand": "5.0.9" diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx index 5dc9a2be..be5145dd 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/InterlinkingLinkInlineContent.tsx @@ -1,21 +1,23 @@ -/* eslint-disable react-hooks/rules-of-hooks */ +import { + PartialCustomInlineContentFromConfig, + StyleSchema, +} from '@blocknote/core'; import { createReactInlineContentSpec } from '@blocknote/react'; +import * as Sentry from '@sentry/nextjs'; import { useRouter } from 'next/router'; import { useEffect } from 'react'; import { css } from 'styled-components'; +import { validate as uuidValidate } from 'uuid'; import { BoxButton, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; import SelectedPageIcon from '@/docs/doc-editor/assets/doc-selected.svg'; -import { getEmojiAndTitle, useDoc } from '@/docs/doc-management'; +import { getEmojiAndTitle, useDoc } from '@/docs/doc-management/'; export const InterlinkingLinkInlineContent = createReactInlineContentSpec( { type: 'interlinkingLinkInline', propSchema: { - url: { - default: '', - }, docId: { default: '', }, @@ -27,46 +29,97 @@ export const InterlinkingLinkInlineContent = createReactInlineContentSpec( }, { render: ({ editor, inlineContent, updateInlineContent }) => { - const { data: doc } = useDoc({ id: inlineContent.props.docId }); - const isEditable = editor.isEditable; + if (!inlineContent.props.docId) { + return null; + } /** - * Update the content title if the referenced doc title changes + * Should not happen */ - useEffect(() => { - if ( - isEditable && - doc?.title && - doc.title !== inlineContent.props.title - ) { - updateInlineContent({ - type: 'interlinkingLinkInline', - props: { - ...inlineContent.props, - title: doc.title, - }, - }); - } + if (!uuidValidate(inlineContent.props.docId)) { + Sentry.captureException( + new Error(`Invalid docId: ${inlineContent.props.docId}`), + { + extra: { info: 'InterlinkingLinkInlineContent' }, + }, + ); - /** - * ⚠️ When doing collaborative editing, doc?.title might be out of sync - * causing an infinite loop of updates. - * To prevent this, we only run this effect when doc?.title changes, - * not when inlineContent.props.title changes. - */ - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [doc?.title, isEditable]); + updateInlineContent({ + type: 'interlinkingLinkInline', + props: { + docId: '', + title: '', + }, + }); - return ; + return null; + } + + return ( + + ); }, }, ); interface LinkSelectedProps { - url: string; + docId: string; title: string; + isEditable: boolean; + updateInlineContent: ( + update: PartialCustomInlineContentFromConfig< + { + readonly type: 'interlinkingLinkInline'; + readonly propSchema: { + readonly docId: { + readonly default: ''; + }; + readonly title: { + readonly default: ''; + }; + }; + readonly content: 'none'; + }, + StyleSchema + >, + ) => void; } -const LinkSelected = ({ url, title }: LinkSelectedProps) => { +export const LinkSelected = ({ + docId, + title, + isEditable, + updateInlineContent, +}: LinkSelectedProps) => { + const { data: doc } = useDoc({ id: docId }); + + /** + * Update the content title if the referenced doc title changes + */ + useEffect(() => { + if (isEditable && doc?.title && doc.title !== title) { + updateInlineContent({ + type: 'interlinkingLinkInline', + props: { + docId, + title: doc.title, + }, + }); + } + + /** + * ⚠️ When doing collaborative editing, doc?.title might be out of sync + * causing an infinite loop of updates. + * To prevent this, we only run this effect when doc?.title changes, + * not when inlineContent.props.title changes. + */ + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [doc?.title, docId, isEditable]); + const { colorsTokens } = useCunninghamTheme(); const { emoji, titleWithoutEmoji } = getEmojiAndTitle(title); @@ -74,7 +127,7 @@ const LinkSelected = ({ url, title }: LinkSelectedProps) => { const handleClick = (e: React.MouseEvent) => { e.preventDefault(); - void router.push(url); + void router.push(`/docs/${docId}/`); }; return ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx index 5ef163cf..47428e75 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/custom-inline-content/Interlinking/SearchPage.tsx @@ -247,7 +247,6 @@ export const SearchPage = ({ { type: 'interlinkingLinkInline', props: { - url: `/docs/${doc.id}`, docId: doc.id, title: doc.title || untitledDocument, }, diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx index afb8c0e7..7da97f08 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkDocx.tsx @@ -1,16 +1,24 @@ import { ExternalHyperlink, TextRun } from 'docx'; +import { getEmojiAndTitle } from '@/docs/doc-management'; + import { DocsExporterDocx } from '../types'; export const inlineContentMappingInterlinkingLinkDocx: DocsExporterDocx['mappings']['inlineContentMapping']['interlinkingLinkInline'] = (inline) => { + if (!inline.props.docId) { + return new TextRun(''); + } + + const { emoji, titleWithoutEmoji } = getEmojiAndTitle(inline.props.title); + return new ExternalHyperlink({ children: [ new TextRun({ - text: `📄${inline.props.title}`, + text: `${emoji || '📄'}${titleWithoutEmoji}`, bold: true, }), ], - link: window.location.origin + inline.props.url, + link: window.location.origin + `/docs/${inline.props.docId}/`, }); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx index 4ffadf08..ef9d3d9a 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkODT.tsx @@ -1,11 +1,17 @@ import React from 'react'; +import { getEmojiAndTitle } from '@/docs/doc-management'; + import { DocsExporterODT } from '../types'; export const inlineContentMappingInterlinkingLinkODT: DocsExporterODT['mappings']['inlineContentMapping']['interlinkingLinkInline'] = (inline) => { - const url = window.location.origin + inline.props.url; - const title = inline.props.title; + if (!inline.props.docId) { + return null; + } + + const { emoji, titleWithoutEmoji } = getEmojiAndTitle(inline.props.title); + const url = window.location.origin + `/docs/${inline.props.docId}/`; // Create ODT hyperlink using React.createElement to avoid TypeScript JSX namespace issues // Uses the same structure as BlockNote's default link mapping @@ -18,6 +24,6 @@ export const inlineContentMappingInterlinkingLinkODT: DocsExporterODT['mappings' xlinkShow: 'replace', xlinkHref: url, }, - `📄${title}`, + `${emoji || '📄'}${titleWithoutEmoji}`, ); }; diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx index c2d204b7..87165dfa 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/inline-content-mapping/interlinkingLinkPDF.tsx @@ -1,21 +1,29 @@ import { Image, Link, Text } from '@react-pdf/renderer'; +import { getEmojiAndTitle } from '@/docs/doc-management'; + import DocSelectedIcon from '../assets/doc-selected.png'; import { DocsExporterPDF } from '../types'; export const inlineContentMappingInterlinkingLinkPDF: DocsExporterPDF['mappings']['inlineContentMapping']['interlinkingLinkInline'] = (inline) => { + if (!inline.props.docId) { + return <>; + } + + const { emoji, titleWithoutEmoji } = getEmojiAndTitle(inline.props.title); + return ( {' '} - {' '} - {inline.props.title}{' '} + {emoji || }{' '} + {titleWithoutEmoji}{' '} ); }; diff --git a/src/frontend/apps/impress/src/i18n/translations.json b/src/frontend/apps/impress/src/i18n/translations.json index 8a239412..45372811 100644 --- a/src/frontend/apps/impress/src/i18n/translations.json +++ b/src/frontend/apps/impress/src/i18n/translations.json @@ -779,12 +779,12 @@ "Document deleted": "Document supprimé", "Document duplicated successfully!": "Document dupliqué avec succès !", "Document editor": "Éditeur de document", + "Document emoji": "Émoji de document", "Document owner": "Propriétaire du document", "Document role text": "Texte du rôle du document", "Document sections": "Sections du document", "Document title": "Titre du document", "Document tree": "Arborescence du document", - "Document viewer": "Visualiseur de document", "Document visibility": "Visibilité du document", "Documents grid": "Grille des documents", "Docx": "Docx", @@ -826,6 +826,7 @@ "Image 403": "Image 403", "Image: {{title}}": "Image : {{title}}", "Insufficient access rights to view the document.": "Droits d'accès insuffisants pour voir le document.", + "Invalid or missing PDF file.": "Fichier PDF non valide ou manquant.", "Invite": "Inviter", "Invite new members": "Inviter de nouveaux membres", "Invite {{count}} members_many": "Inviter {{count}} membres", @@ -884,6 +885,7 @@ "Others are editing. Your network prevent changes.": "D'autres sont en cours d'édition. Votre réseau empêche les changements.", "Owner": "Propriétaire", "PDF": "PDF", + "PDF document": "Document PDF", "Page Not Found - Error 404": "Page introuvable - Erreur 404", "Pending invitations": "Invitations en attente", "People with access via the parent document": "Personnes ayant accès au document parent", @@ -1222,7 +1224,6 @@ "Document sections": "Document secties", "Document title": "Documenttitel", "Document tree": "Boomstructuur document", - "Document viewer": "Document-viewer", "Document visibility": "Document toegankelijkheid", "Documents grid": "Documenten overzicht", "Docx": "Docx", @@ -1264,6 +1265,7 @@ "Image 403": "Afbeelding 403", "Image: {{title}}": "Afbeelding: {{title}}", "Insufficient access rights to view the document.": "Onvoldoende toegangsrechten om het document te bekijken.", + "Invalid or missing PDF file.": "Ongeldig of ontbrekend PDF-bestand.", "Invite": "Uitnodigen", "Invite new members": "Nieuwe leden uitnodigen", "Invite {{count}} members_many": "Nodig {{count}} leden uit", @@ -1322,6 +1324,7 @@ "Others are editing. Your network prevent changes.": "Anderen zijn aan het bewerken. Uw netwerk voorkomt wijzigingen.", "Owner": "Eigenaar", "PDF": "PDF", + "PDF document": "Pdf-document", "Page Not Found - Error 404": "Pagina niet gevonden - Fout 404", "Pending invitations": "Openstaande uitnodigingen", "People with access via the parent document": "Gebruikers met toegang via het bovenliggend document", @@ -1516,12 +1519,12 @@ "Document deleted": "Документ удалён", "Document duplicated successfully!": "Документ успешно дублирован!", "Document editor": "Редактор документа", + "Document emoji": "Эмодзи документа", "Document owner": "Владелец документа", "Document role text": "Текст роли документа", "Document sections": "Разделы документа", "Document title": "Заголовок документа", "Document tree": "Иерархия документа", - "Document viewer": "Просмотрщик документа", "Document visibility": "Видимость документа", "Documents grid": "Сетка документов", "Docx": "Docx", @@ -1563,6 +1566,7 @@ "Image 403": "Изображение 403", "Image: {{title}}": "Изображение: {{title}}", "Insufficient access rights to view the document.": "Недостаточно прав доступа для просмотра документа.", + "Invalid or missing PDF file.": "Повреждённый или отсутствующий PDF-файл.", "Invite": "Приглашение", "Invite new members": "Пригласить новых участников", "Invite {{count}} members_many": "Пригласить {{count}} участников", @@ -1621,6 +1625,7 @@ "Others are editing. Your network prevent changes.": "Другие участники редактируют этот документ. Ваша сеть не позволяет вам присоединиться.", "Owner": "Владелец", "PDF": "PDF", + "PDF document": "Документ PDF", "Page Not Found - Error 404": "Страница не найдена - ошибка 404", "Pending invitations": "Ожидающие приглашения", "People with access via the parent document": "Люди, имеющие доступ через родительский документ", @@ -1918,12 +1923,12 @@ "Document deleted": "Документ видалено", "Document duplicated successfully!": "Документ успішно продубльовано!", "Document editor": "Редактор документа", + "Document emoji": "Емодзі документу", "Document owner": "Власник документа", "Document role text": "Текст ролі документа", "Document sections": "Розділи документу", "Document title": "Назва документа", "Document tree": "Дерево документа", - "Document viewer": "Переглядач документа", "Document visibility": "Видимість документа", "Documents grid": "Сітка документів", "Docx": "Docx", @@ -1965,6 +1970,7 @@ "Image 403": "Зображення 403", "Image: {{title}}": "Зображення: {{title}}", "Insufficient access rights to view the document.": "Недостатньо прав для перегляду документа.", + "Invalid or missing PDF file.": "Неприпустимий або відсутній PDF-файл.", "Invite": "Запрошення", "Invite new members": "Запросити нових учасників", "Invite {{count}} members_many": "Запросити {{count}} учасників", @@ -2023,6 +2029,7 @@ "Others are editing. Your network prevent changes.": "Інші учасники редагують документ. Ваша мережа не дозволяє вам вносити зміни.", "Owner": "Власник", "PDF": "PDF", + "PDF document": "Документ PDF", "Page Not Found - Error 404": "Сторінку не знайдено - Помилка 404", "Pending invitations": "Запрошення в очікуванні", "People with access via the parent document": "Люди з доступом через батьківський документ", diff --git a/src/frontend/package.json b/src/frontend/package.json index 9673dfe6..d1260f69 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -1,6 +1,6 @@ { "name": "impress", - "version": "4.3.0", + "version": "4.4.0", "private": true, "repository": "https://github.com/suitenumerique/docs", "author": "DINUM", diff --git a/src/frontend/packages/eslint-plugin-docs/package.json b/src/frontend/packages/eslint-plugin-docs/package.json index e46a5415..d2a418e5 100644 --- a/src/frontend/packages/eslint-plugin-docs/package.json +++ b/src/frontend/packages/eslint-plugin-docs/package.json @@ -1,6 +1,6 @@ { "name": "eslint-plugin-docs", - "version": "4.3.0", + "version": "4.4.0", "repository": "https://github.com/suitenumerique/docs", "author": "DINUM", "license": "MIT", diff --git a/src/frontend/packages/i18n/package.json b/src/frontend/packages/i18n/package.json index 06270622..a9f7d4e1 100644 --- a/src/frontend/packages/i18n/package.json +++ b/src/frontend/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "packages-i18n", - "version": "4.3.0", + "version": "4.4.0", "repository": "https://github.com/suitenumerique/docs", "author": "DINUM", "license": "MIT", diff --git a/src/frontend/servers/y-provider/package.json b/src/frontend/servers/y-provider/package.json index bc4c9b63..44639fc9 100644 --- a/src/frontend/servers/y-provider/package.json +++ b/src/frontend/servers/y-provider/package.json @@ -1,6 +1,6 @@ { "name": "server-y-provider", - "version": "4.3.0", + "version": "4.4.0", "description": "Y.js provider for docs", "repository": "https://github.com/suitenumerique/docs", "license": "MIT", diff --git a/src/helm/helmfile.yaml.gotmpl b/src/helm/helmfile.yaml.gotmpl index 445601c3..a815ad6e 100644 --- a/src/helm/helmfile.yaml.gotmpl +++ b/src/helm/helmfile.yaml.gotmpl @@ -1,10 +1,10 @@ environments: dev: values: - - version: 4.3.0 + - version: 4.4.0 feature: values: - - version: 4.3.0 + - version: 4.4.0 feature: ci domain: example.com imageTag: demo diff --git a/src/helm/impress/Chart.yaml b/src/helm/impress/Chart.yaml index 57487a2f..4697c5ee 100644 --- a/src/helm/impress/Chart.yaml +++ b/src/helm/impress/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 type: application name: docs -version: 4.3.0 +version: 4.4.0 appVersion: latest diff --git a/src/mail/package.json b/src/mail/package.json index 0b3b6597..0e6829e7 100644 --- a/src/mail/package.json +++ b/src/mail/package.json @@ -1,6 +1,6 @@ { "name": "mail_mjml", - "version": "4.3.0", + "version": "4.4.0", "description": "An util to generate html and text django's templates from mjml templates", "type": "module", "dependencies": {